// 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);
}
}
}