// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.bookmarks; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.chrome.R; import org.chromium.chrome.browser.SynchronousInitializationActivity; import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem; import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver; import org.chromium.chrome.browser.widget.TintedDrawable; import org.chromium.components.bookmarks.BookmarkId; import java.util.ArrayList; import java.util.List; /** * Dialog for moving bookmarks from one folder to another. A list of folders are shown and the * hierarchy of bookmark model is presented by indentation of list items. This dialog can be shown * in two cases. One is when user choose to move an existing bookmark to a new folder. The other is * when user creates a new folder/bookmark, he/she can choose which parent the new folder/bookmark * belong to. * <p> * Note this fragment will not be restarted by OS. It will be dismissed if chrome is killed in * background. */ public class BookmarkFolderSelectActivity extends SynchronousInitializationActivity implements AdapterView.OnItemClickListener { static final String INTENT_SELECTED_FOLDER = "BookmarkFolderSelectActivity.selectedFolder"; static final String INTENT_IS_CREATING_FOLDER = "BookmarkFolderSelectActivity.isCreatingFolder"; static final String INTENT_BOOKMARKS_TO_MOVE = "BookmarkFolderSelectActivity.bookmarksToMove"; static final int CREATE_FOLDER_REQUEST_CODE = 13; private BookmarkModel mModel; private boolean mIsCreatingFolder; private List<BookmarkId> mBookmarksToMove; private BookmarkId mParentId; private FolderListAdapter mBookmarkIdsAdapter; private ListView mBookmarkIdsList; private BookmarkModelObserver mBookmarkModelObserver = new BookmarkModelObserver() { @Override public void bookmarkModelChanged() { updateFolderList(); } @Override public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node, boolean isDoingExtensiveChanges) { if (mBookmarksToMove.contains(node.getId())) { mBookmarksToMove.remove(node.getId()); if (mBookmarksToMove.isEmpty()) { finish(); return; } } else if (node.isFolder()) { updateFolderList(); } } }; /** * Starts a select folder activity. */ public static void startFolderSelectActivity(Context context, BookmarkId... bookmarks) { assert bookmarks.length > 0; Intent intent = new Intent(context, BookmarkFolderSelectActivity.class); intent.putExtra(INTENT_IS_CREATING_FOLDER, false); ArrayList<String> bookmarkStrings = new ArrayList<>(bookmarks.length); for (BookmarkId id : bookmarks) { bookmarkStrings.add(id.toString()); } intent.putStringArrayListExtra(INTENT_BOOKMARKS_TO_MOVE, bookmarkStrings); context.startActivity(intent); } /** * Starts a select folder activity for the new folder that is about to be created. This method * is only supposed to be called by {@link BookmarkAddEditFolderActivity} */ public static void startNewFolderSelectActivity( BookmarkAddEditFolderActivity activity, List<BookmarkId> bookmarks) { assert bookmarks.size() > 0; Intent intent = new Intent(activity, BookmarkFolderSelectActivity.class); intent.putExtra(INTENT_IS_CREATING_FOLDER, true); ArrayList<String> bookmarkStrings = new ArrayList<>(bookmarks.size()); for (BookmarkId id : bookmarks) { bookmarkStrings.add(id.toString()); } intent.putStringArrayListExtra(INTENT_BOOKMARKS_TO_MOVE, bookmarkStrings); activity.startActivityForResult(intent, BookmarkAddEditFolderActivity.PARENT_FOLDER_REQUEST_CODE); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mModel = new BookmarkModel(); mModel.addObserver(mBookmarkModelObserver); List<String> stringList = getIntent().getStringArrayListExtra(INTENT_BOOKMARKS_TO_MOVE); mBookmarksToMove = new ArrayList<>(stringList.size()); for (String string : stringList) { BookmarkId bookmarkId = BookmarkId.getBookmarkIdFromString(string); if (mModel.doesBookmarkExist(bookmarkId)) { mBookmarksToMove.add(bookmarkId); } } if (mBookmarksToMove.isEmpty()) { finish(); return; } mIsCreatingFolder = getIntent().getBooleanExtra(INTENT_IS_CREATING_FOLDER, false); if (mIsCreatingFolder) { mParentId = mModel.getMobileFolderId(); } else { mParentId = mModel.getBookmarkById(mBookmarksToMove.get(0)) .getParentId(); } setContentView(R.layout.bookmark_folder_select_activity); mBookmarkIdsList = (ListView) findViewById(R.id.bookmark_folder_list); mBookmarkIdsList.setOnItemClickListener(this); mBookmarkIdsAdapter = new FolderListAdapter(this); mBookmarkIdsList.setAdapter(mBookmarkIdsAdapter); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); updateFolderList(); } private void updateFolderList() { List<BookmarkId> folderList = new ArrayList<>(); List<Integer> depthList = new ArrayList<>(); mModel.getMoveDestinations(folderList, depthList, mBookmarksToMove); List<FolderListEntry> entryList = new ArrayList<>(folderList.size() + 3); if (!mIsCreatingFolder) { entryList.add(new FolderListEntry(null, 0, getString(R.string.bookmark_add_folder), false, FolderListEntry.TYPE_NEW_FOLDER)); } for (int i = 0; i < folderList.size(); i++) { BookmarkId folder = folderList.get(i); if (!mModel.isFolderVisible(folder)) continue; String title = mModel.getBookmarkById(folder).getTitle(); entryList.add(new FolderListEntry(folder, depthList.get(i), title, folder.equals(mParentId), FolderListEntry.TYPE_NORMAL)); } mBookmarkIdsAdapter.setEntryList(entryList); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { onBackPressed(); return true; } return super.onOptionsItemSelected(item); } @Override protected void onDestroy() { super.onDestroy(); mModel.removeObserver(mBookmarkModelObserver); mModel.destroy(); mModel = null; } /** * Moves bookmark from original parent to selected folder. In creation mode, directly add the * new bookmark to selected folder instead of moving. */ @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { FolderListEntry entry = (FolderListEntry) adapter.getItemAtPosition(position); if (mIsCreatingFolder) { BookmarkId selectedFolder = null; if (entry.mType == FolderListEntry.TYPE_NORMAL) { selectedFolder = entry.mId; } else { assert false : "New folder items should not be clickable in creating mode"; } Intent intent = new Intent(); intent.putExtra(INTENT_SELECTED_FOLDER, selectedFolder.toString()); setResult(RESULT_OK, intent); finish(); } else if (entry.mType == FolderListEntry.TYPE_NEW_FOLDER) { BookmarkAddEditFolderActivity.startAddFolderActivity(this, mBookmarksToMove); } else if (entry.mType == FolderListEntry.TYPE_NORMAL) { mModel.moveBookmarks(mBookmarksToMove, entry.mId); BookmarkUtils.setLastUsedParent(this, entry.mId); finish(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); assert !mIsCreatingFolder; if (requestCode == CREATE_FOLDER_REQUEST_CODE && resultCode == RESULT_OK) { BookmarkId createdBookmark = BookmarkId.getBookmarkIdFromString(data.getStringExtra( BookmarkAddEditFolderActivity.INTENT_CREATED_BOOKMARK)); mModel.moveBookmarks(mBookmarksToMove, createdBookmark); BookmarkUtils.setLastUsedParent(this, createdBookmark); finish(); } } /** * Data object representing a folder entry used in FolderListAdapter. */ private static class FolderListEntry { public static final int TYPE_NEW_FOLDER = 0; public static final int TYPE_NORMAL = 1; BookmarkId mId; String mTitle; int mDepth; boolean mIsSelected; int mType; public FolderListEntry(BookmarkId bookmarkId, int depth, String title, boolean isSelected, int type) { assert type == TYPE_NEW_FOLDER || type == TYPE_NORMAL; mDepth = depth; mId = bookmarkId; mTitle = title; mIsSelected = isSelected; mType = type; } } private static class FolderListAdapter extends BaseAdapter { // The maximum depth that will be indented. Folders with a depth greater // than this will all appear at this same depth. private static final int MAX_FOLDER_DEPTH = 5; private final int mBasePadding; private final int mPaddingIncrement; List<FolderListEntry> mEntryList = new ArrayList<>(); public FolderListAdapter(Context context) { mBasePadding = context.getResources() .getDimensionPixelSize(R.dimen.bookmark_folder_item_left); mPaddingIncrement = mBasePadding * 2; } @Override public int getCount() { return mEntryList.size(); } @Override public FolderListEntry getItem(int position) { return mEntryList.get(position); } @Override public long getItemId(int position) { return position; } /** * There are 2 types of entries: new folder and normal. */ @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int position) { FolderListEntry entry = getItem(position); return entry.mType; } @Override public View getView(int position, View convertView, ViewGroup parent) { final FolderListEntry entry = getItem(position); if (convertView != null && entry.mType != FolderListEntry.TYPE_NORMAL) { return convertView; } if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()).inflate( R.layout.bookmark_folder_select_item, parent, false); } TextView textView = (TextView) convertView; textView.setText(entry.mTitle); setUpIcons(entry, textView); setUpPadding(entry, textView); return textView; } void setEntryList(List<FolderListEntry> entryList) { mEntryList = entryList; notifyDataSetChanged(); } /** * Sets compound drawables (icons) for different kinds of list entries, * i.e. New Folder, Normal and Selected. */ private void setUpIcons(FolderListEntry entry, TextView textView) { int iconId = 0; if (entry.mType == FolderListEntry.TYPE_NORMAL) { iconId = R.drawable.bookmark_folder; } else if (entry.mType == FolderListEntry.TYPE_NEW_FOLDER) { // For new folder, start_icon is different. iconId = R.drawable.bookmark_add_folder; } Drawable drawableStart = TintedDrawable.constructTintedDrawable(textView.getResources(), iconId); // Selected entry has an end_icon, a blue check mark. Drawable drawableEnd = entry.mIsSelected ? ApiCompatibilityUtils.getDrawable( textView.getResources(), R.drawable.ic_check_googblue_24dp) : null; ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, drawableStart, null, drawableEnd, null); } /** * Sets up padding for the entry */ private void setUpPadding(FolderListEntry entry, TextView textView) { int paddingStart = mBasePadding + Math.min(entry.mDepth, MAX_FOLDER_DEPTH) * mPaddingIncrement; ApiCompatibilityUtils.setPaddingRelative(textView, paddingStart, 0, mBasePadding, 0); } } }