summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/home
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-04-23 15:32:23 -0400
committerMatt A. Tobin <email@mattatobin.com>2019-04-23 15:32:23 -0400
commitabe80cc31d5a40ebed743085011fbcda0c1a9a10 (patch)
treefb3762f06b84745b182af281abb107b95a9fcf01 /mobile/android/base/java/org/mozilla/gecko/home
parent63295d0087eb58a6eb34cad324c4c53d1b220491 (diff)
downloadUXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.gz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.lz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.xz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.zip
Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/home')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java147
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java67
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java352
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java218
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java316
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java1316
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java373
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java433
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java697
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java145
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java393
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java80
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java224
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java315
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java1694
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java83
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java663
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java82
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java68
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java498
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java138
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomePager.java564
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java368
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java57
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java164
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java100
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java82
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java63
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java48
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java28
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java162
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java136
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java747
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java83
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java178
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java137
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java90
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java113
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java59
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java256
-rwxr-xr-xmobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java454
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java163
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java122
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java148
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java494
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java114
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java147
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java20
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java246
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java312
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java169
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java968
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java324
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java145
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java73
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java196
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java135
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java239
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java76
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java568
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java105
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java117
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java124
71 files changed, 0 insertions, 17885 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
deleted file mode 100644
index 566422faf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-public class BookmarkFolderView extends LinearLayout {
- private static final Set<Integer> FOLDERS_WITH_COUNT;
-
- static {
- final Set<Integer> folders = new TreeSet<>();
- folders.add(BrowserContract.Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID);
-
- FOLDERS_WITH_COUNT = Collections.unmodifiableSet(folders);
- }
-
- public enum FolderState {
- /**
- * A standard folder, i.e. a folder in a list of bookmarks and folders.
- */
- FOLDER(R.drawable.folder_closed),
-
- /**
- * The parent folder: this indicates that you are able to return to the previous
- * folder ("Back to {name}").
- */
- PARENT(R.drawable.bookmark_folder_arrow_up),
-
- /**
- * The reading list smartfolder: this displays a reading list icon instead of the
- * normal folder icon.
- */
- READING_LIST(R.drawable.reading_list_folder);
-
- public final int image;
-
- FolderState(final int image) { this.image = image; }
- }
-
- private final TextView mTitle;
- private final TextView mSubtitle;
-
- private final ImageView mIcon;
-
- public BookmarkFolderView(Context context) {
- this(context, null);
- }
-
- public BookmarkFolderView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.two_line_folder_row, this);
-
- mTitle = (TextView) findViewById(R.id.title);
- mSubtitle = (TextView) findViewById(R.id.subtitle);
- mIcon = (ImageView) findViewById(R.id.icon);
- }
-
- public void update(String title, int folderID) {
- setTitle(title);
- setID(folderID);
- }
-
- private void setTitle(String title) {
- mTitle.setText(title);
- }
-
- private static class ItemCountUpdateTask extends UIAsyncTask.WithoutParams<Integer> {
- private final WeakReference<TextView> mTextViewReference;
- private final int mFolderID;
-
- public ItemCountUpdateTask(final WeakReference<TextView> textViewReference,
- final int folderID) {
- super(ThreadUtils.getBackgroundHandler());
-
- mTextViewReference = textViewReference;
- mFolderID = folderID;
- }
-
- @Override
- protected Integer doInBackground() {
- final TextView textView = mTextViewReference.get();
-
- if (textView == null) {
- return null;
- }
-
- final BrowserDB db = BrowserDB.from(textView.getContext());
- return db.getBookmarkCountForFolder(textView.getContext().getContentResolver(), mFolderID);
- }
-
- @Override
- protected void onPostExecute(Integer count) {
- final TextView textView = mTextViewReference.get();
-
- if (textView == null) {
- return;
- }
-
- final String text;
- if (count == 1) {
- text = textView.getContext().getResources().getString(R.string.bookmark_folder_one_item);
- } else {
- text = textView.getContext().getResources().getString(R.string.bookmark_folder_items, count);
- }
-
- textView.setText(text);
- textView.setVisibility(View.VISIBLE);
- }
- }
-
- private void setID(final int folderID) {
- if (FOLDERS_WITH_COUNT.contains(folderID)) {
- final WeakReference<TextView> subTitleReference = new WeakReference<TextView>(mSubtitle);
-
- new ItemCountUpdateTask(subTitleReference, folderID).execute();
- } else {
- mSubtitle.setVisibility(View.GONE);
- }
- }
-
- public void setState(@NonNull FolderState state) {
- mIcon.setImageResource(state.image);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java
deleted file mode 100644
index a1efff049..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
-
-import java.text.DateFormat;
-import java.text.FieldPosition;
-import java.util.Date;
-
-/**
- * An entry of the screenshot list in the bookmarks panel.
- */
-class BookmarkScreenshotRow extends LinearLayout {
- private TextView titleView;
- private TextView dateView;
-
- // This DateFormat uses the current locale at instantiation time, which won't get updated if the locale is changed.
- // Since it's just a date, it's probably not worth the code complexity to fix that.
- private static final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG);
- private static final DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
-
- // This parameter to DateFormat.format has no impact on the result but rather gets mutated by the method to
- // identify where a certain field starts and ends (by index). This is useful if you want to later modify the String;
- // I'm not sure why this argument isn't optional.
- private static final FieldPosition dummyFieldPosition = new FieldPosition(-1);
-
- public BookmarkScreenshotRow(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- titleView = (TextView) findViewById(R.id.title);
- dateView = (TextView) findViewById(R.id.date);
- }
-
- public void updateFromCursor(final Cursor c) {
- titleView.setText(getTitleFromCursor(c));
- dateView.setText(getDateFromCursor(c));
- }
-
- private static String getTitleFromCursor(final Cursor c) {
- final int index = c.getColumnIndexOrThrow(UrlAnnotations.URL);
- return c.getString(index);
- }
-
- private static String getDateFromCursor(final Cursor c) {
- final long timestamp = c.getLong(c.getColumnIndexOrThrow(UrlAnnotations.DATE_CREATED));
- final Date date = new Date(timestamp);
- final StringBuffer sb = new StringBuffer();
- dateFormat.format(date, sb, dummyFieldPosition)
- .append(" - ");
- timeFormat.format(date, sb, dummyFieldPosition);
- return sb.toString();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
deleted file mode 100644
index b31116693..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.home.BookmarkFolderView.FolderState;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.View;
-
-/**
- * Adapter to back the BookmarksListView with a list of bookmarks.
- */
-class BookmarksListAdapter extends MultiTypeCursorAdapter {
- private static final int VIEW_TYPE_BOOKMARK_ITEM = 0;
- private static final int VIEW_TYPE_FOLDER = 1;
- private static final int VIEW_TYPE_SCREENSHOT = 2;
-
- private static final int[] VIEW_TYPES = new int[] { VIEW_TYPE_BOOKMARK_ITEM, VIEW_TYPE_FOLDER, VIEW_TYPE_SCREENSHOT };
- private static final int[] LAYOUT_TYPES =
- new int[] { R.layout.bookmark_item_row, R.layout.bookmark_folder_row, R.layout.bookmark_screenshot_row };
-
- public enum RefreshType implements Parcelable {
- PARENT,
- CHILD;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<RefreshType> CREATOR = new Creator<RefreshType>() {
- @Override
- public RefreshType createFromParcel(final Parcel source) {
- return RefreshType.values()[source.readInt()];
- }
-
- @Override
- public RefreshType[] newArray(final int size) {
- return new RefreshType[size];
- }
- };
- }
-
- public static class FolderInfo implements Parcelable {
- public final int id;
- public final String title;
-
- public FolderInfo(int id) {
- this(id, "");
- }
-
- public FolderInfo(Parcel in) {
- this(in.readInt(), in.readString());
- }
-
- public FolderInfo(int id, String title) {
- this.id = id;
- this.title = title;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeString(title);
- }
-
- public static final Creator<FolderInfo> CREATOR = new Creator<FolderInfo>() {
- @Override
- public FolderInfo createFromParcel(Parcel in) {
- return new FolderInfo(in);
- }
-
- @Override
- public FolderInfo[] newArray(int size) {
- return new FolderInfo[size];
- }
- };
- }
-
- // A listener that knows how to refresh the list for a given folder id.
- // This is usually implemented by the enclosing fragment/activity.
- public static interface OnRefreshFolderListener {
- // The folder id to refresh the list with.
- public void onRefreshFolder(FolderInfo folderInfo, RefreshType refreshType);
- }
-
- /**
- * The type of data a bookmarks folder can display. This can be used to
- * distinguish bookmark folders from "smart folders" that contain non-bookmark
- * entries but still appear in the Bookmarks panel.
- */
- public enum FolderType {
- BOOKMARKS,
- SCREENSHOTS,
- }
-
- // mParentStack holds folder info instances (id + title) that allow
- // us to navigate back up the folder hierarchy.
- private LinkedList<FolderInfo> mParentStack;
-
- // Refresh folder listener.
- private OnRefreshFolderListener mListener;
-
- private FolderType openFolderType = FolderType.BOOKMARKS;
-
- public BookmarksListAdapter(Context context, Cursor cursor, List<FolderInfo> parentStack) {
- // Initializing with a null cursor.
- super(context, cursor, VIEW_TYPES, LAYOUT_TYPES);
-
- if (parentStack == null) {
- mParentStack = new LinkedList<FolderInfo>();
- } else {
- mParentStack = new LinkedList<FolderInfo>(parentStack);
- }
- }
-
- public void restoreData(List<FolderInfo> parentStack) {
- mParentStack = new LinkedList<FolderInfo>(parentStack);
- notifyDataSetChanged();
- }
-
- public List<FolderInfo> getParentStack() {
- return Collections.unmodifiableList(mParentStack);
- }
-
- public FolderType getOpenFolderType() {
- return openFolderType;
- }
-
- /**
- * Moves to parent folder, if one exists.
- *
- * @return Whether the adapter successfully moved to a parent folder.
- */
- public boolean moveToParentFolder() {
- // If we're already at the root, we can't move to a parent folder.
- // An empty parent stack here means we're still waiting for the
- // initial list of bookmarks and can't go to a parent folder.
- if (mParentStack.size() <= 1) {
- return false;
- }
-
- if (mListener != null) {
- // We pick the second folder in the stack as it represents
- // the parent folder.
- mListener.onRefreshFolder(mParentStack.get(1), RefreshType.PARENT);
- }
-
- return true;
- }
-
- /**
- * Moves to child folder, given a folderId.
- *
- * @param folderId The id of the folder to show.
- * @param folderTitle The title of the folder to show.
- */
- public void moveToChildFolder(int folderId, String folderTitle) {
- FolderInfo folderInfo = new FolderInfo(folderId, folderTitle);
-
- if (mListener != null) {
- mListener.onRefreshFolder(folderInfo, RefreshType.CHILD);
- }
- }
-
- /**
- * Set a listener that can refresh this adapter.
- *
- * @param listener The listener that can refresh the adapter.
- */
- public void setOnRefreshFolderListener(OnRefreshFolderListener listener) {
- mListener = listener;
- }
-
- private boolean isCurrentFolder(FolderInfo folderInfo) {
- return (mParentStack.size() > 0 &&
- mParentStack.peek().id == folderInfo.id);
- }
-
- public void swapCursor(Cursor c, FolderInfo folderInfo, RefreshType refreshType) {
- updateOpenFolderType(folderInfo);
- switch (refreshType) {
- case PARENT:
- if (!isCurrentFolder(folderInfo)) {
- mParentStack.removeFirst();
- }
- break;
-
- case CHILD:
- if (!isCurrentFolder(folderInfo)) {
- mParentStack.addFirst(folderInfo);
- }
- break;
-
- default:
- // Do nothing;
- }
-
- swapCursor(c);
- }
-
- private void updateOpenFolderType(final FolderInfo folderInfo) {
- if (folderInfo.id == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
- openFolderType = FolderType.SCREENSHOTS;
- } else {
- openFolderType = FolderType.BOOKMARKS;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- // The position also reflects the opened child folder row.
- if (isShowingChildFolder()) {
- if (position == 0) {
- return VIEW_TYPE_FOLDER;
- }
-
- // Accounting for the folder view.
- position--;
- }
-
- if (openFolderType == FolderType.SCREENSHOTS) {
- return VIEW_TYPE_SCREENSHOT;
- }
-
- final Cursor c = getCursor(position);
- if (c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) {
- return VIEW_TYPE_FOLDER;
- }
-
- // Default to returning normal item type.
- return VIEW_TYPE_BOOKMARK_ITEM;
- }
-
- /**
- * Get the title of the folder given a cursor moved to the position.
- *
- * @param context The context of the view.
- * @param cursor A cursor moved to the required position.
- * @return The title of the folder at the position.
- */
- public String getFolderTitle(Context context, Cursor c) {
- String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID));
-
- // If we don't have a special GUID, just return the folder title from the DB.
- if (guid == null || guid.length() == 12) {
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
- }
-
- Resources res = context.getResources();
-
- // Use localized strings for special folder names.
- if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_desktop);
- } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_menu);
- } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_toolbar);
- } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_unfiled);
- } else if (guid.equals(Bookmarks.SCREENSHOT_FOLDER_GUID)) {
- return res.getString(R.string.screenshot_folder_label_in_bookmarks);
- } else if (guid.equals(Bookmarks.FAKE_READINGLIST_SMARTFOLDER_GUID)) {
- return res.getString(R.string.readinglist_smartfolder_label_in_bookmarks);
- }
-
- // If for some reason we have a folder with a special GUID, but it's not one of
- // the special folders we expect in the UI, just return the title from the DB.
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
- }
-
- /**
- * @return true, if currently showing a child folder, false otherwise.
- */
- public boolean isShowingChildFolder() {
- if (mParentStack.size() == 0) {
- return false;
- }
-
- return (mParentStack.peek().id != Bookmarks.FIXED_ROOT_ID);
- }
-
- @Override
- public int getCount() {
- return super.getCount() + (isShowingChildFolder() ? 1 : 0);
- }
-
- @Override
- public void bindView(View view, Context context, int position) {
- final int viewType = getItemViewType(position);
-
- final Cursor cursor;
- if (isShowingChildFolder()) {
- if (position == 0) {
- cursor = null;
- } else {
- // Accounting for the folder view.
- position--;
- cursor = getCursor(position);
- }
- } else {
- cursor = getCursor(position);
- }
-
- if (viewType == VIEW_TYPE_SCREENSHOT) {
- ((BookmarkScreenshotRow) view).updateFromCursor(cursor);
- } else if (viewType == VIEW_TYPE_BOOKMARK_ITEM) {
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(cursor);
- } else {
- final BookmarkFolderView row = (BookmarkFolderView) view;
- if (cursor == null) {
- final Resources res = context.getResources();
- row.update(res.getString(R.string.home_move_back_to_filter, mParentStack.get(1).title), -1);
- row.setState(FolderState.PARENT);
- } else {
- int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
-
- row.update(getFolderTitle(context, cursor), id);
-
- if (id == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- row.setState(FolderState.READING_LIST);
- } else {
- row.setState(FolderState.FOLDER);
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
deleted file mode 100644
index 94157be10..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
+++ /dev/null
@@ -1,218 +0,0 @@
- /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-import java.util.List;
-
-import android.util.Log;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.HeaderViewListAdapter;
-import android.widget.ListAdapter;
-
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.util.NetworkUtils;
-
-/**
- * A ListView of bookmarks.
- */
-public class BookmarksListView extends HomeListView
- implements AdapterView.OnItemClickListener {
- public static final String LOGTAG = "GeckoBookmarksListView";
-
- public BookmarksListView(Context context) {
- this(context, null);
- }
-
- public BookmarksListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.bookmarksListViewStyle);
- }
-
- public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- setOnItemClickListener(this);
-
- setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- final int action = event.getAction();
-
- // If the user hit the BACK key, try to move to the parent folder.
- if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return getBookmarksListAdapter().moveToParentFolder();
- }
- return false;
- }
- });
- }
-
- /**
- * Get the appropriate telemetry extra for a given folder.
- *
- * baseFolderID is the ID of the first-level folder in the parent stack, i.e. the first folder
- * that was selected from the root hierarchy (e.g. Desktop, Reading List, or any mobile first-level
- * subfolder). If the current folder is a first-level folder, then the fixed root ID may be used
- * instead.
- *
- * We use baseFolderID only to distinguish whether or not we're currently in a desktop subfolder.
- * If it isn't equal to FAKE_DESKTOP_FOLDER_ID we know we're in a mobile subfolder, or one
- * of the smartfolders.
- */
- private String getTelemetryExtraForFolder(int folderID, int baseFolderID) {
- if (folderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
- return "folder_desktop";
- } else if (folderID == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
- return "folder_screenshots";
- } else if (folderID == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- return "folder_reading_list";
- } else {
- // The stack depth is 2 for either the fake desktop folder, or any subfolder of mobile
- // bookmarks, we subtract these offsets so that any direct subfolder of mobile
- // has a level equal to 1. (Desktop folders will be one level deeper due to the
- // fake desktop folder, hence subtract 2.)
- if (baseFolderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
- return "folder_desktop_subfolder";
- } else {
- return "folder_mobile_subfolder";
- }
- }
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final BookmarksListAdapter adapter = getBookmarksListAdapter();
- if (adapter.isShowingChildFolder()) {
- if (position == 0) {
- // If we tap on an opened folder, move back to parent folder.
-
- final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
- if (parentStack.size() < 2) {
- throw new IllegalStateException("Cannot move to parent folder if we are already in the root folder");
- }
-
- // The first item (top of stack) is the current folder, we're returning to the next one
- BookmarksListAdapter.FolderInfo folder = parentStack.get(1);
- final int parentID = folder.id;
- final int baseFolderID;
- if (parentStack.size() > 2) {
- baseFolderID = parentStack.get(parentStack.size() - 2).id;
- } else {
- baseFolderID = Bookmarks.FIXED_ROOT_ID;
- }
-
- final String extra = getTelemetryExtraForFolder(parentID, baseFolderID);
-
- // Move to parent _after_ retrieving stack information
- adapter.moveToParentFolder();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
- return;
- }
-
- // Accounting for the folder view.
- position--;
- }
-
- final Cursor cursor = adapter.getCursor();
- if (cursor == null) {
- return;
- }
-
- cursor.moveToPosition(position);
-
- if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "bookmarks-screenshot");
-
- final String fileUrl = "file://" + cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.VALUE));
- getOnUrlOpenListener().onUrlOpen(fileUrl, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- return;
- }
-
- int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
- if (type == Bookmarks.TYPE_FOLDER) {
- // If we're clicking on a folder, update adapter to move to that folder
- final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
- final String folderTitle = adapter.getFolderTitle(parent.getContext(), cursor);
- adapter.moveToChildFolder(folderId, folderTitle);
-
- final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
-
- final int baseFolderID;
- if (parentStack.size() > 2) {
- baseFolderID = parentStack.get(parentStack.size() - 2).id;
- } else {
- baseFolderID = Bookmarks.FIXED_ROOT_ID;
- }
-
- final String extra = getTelemetryExtraForFolder(folderId, baseFolderID);
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
- } else {
- // Otherwise, just open the URL
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
-
- final SavedReaderViewHelper rvh = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
-
- final String extra;
- if (rvh.isURLCached(url)) {
- extra = "bookmarks-reader";
- } else {
- extra = "bookmarks";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, extra);
- Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(getContext()) ? 2 : 3);
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Adjust the item position to account for the parent folder row that is inserted
- // at the top of the list when viewing the contents of a folder.
- final BookmarksListAdapter adapter = getBookmarksListAdapter();
- if (adapter.isShowingChildFolder()) {
- position--;
- }
-
- // Temporarily prevent crashes until we figure out what we actually want to do here (bug 1252316).
- if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
- return false;
- }
-
- return super.onItemLongClick(parent, view, position, id);
- }
-
- private BookmarksListAdapter getBookmarksListAdapter() {
- BookmarksListAdapter adapter;
- ListAdapter listAdapter = getAdapter();
- if (listAdapter instanceof HeaderViewListAdapter) {
- adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
- } else {
- adapter = (BookmarksListAdapter) listAdapter;
- }
- return adapter;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
deleted file mode 100644
index 4b4781996..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
-import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
-import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.database.MergeCursor;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.annotation.NonNull;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * A page in about:home that displays a ListView of bookmarks.
- */
-public class BookmarksPanel extends HomeFragment {
- public static final String LOGTAG = "GeckoBookmarksPanel";
-
- // Cursor loader ID for list of bookmarks.
- private static final int LOADER_ID_BOOKMARKS_LIST = 0;
-
- // Information about the target bookmarks folder.
- private static final String BOOKMARKS_FOLDER_INFO = "folder_info";
-
- // Refresh type for folder refreshing loader.
- private static final String BOOKMARKS_REFRESH_TYPE = "refresh_type";
-
- // List of bookmarks.
- private BookmarksListView mList;
-
- // Adapter for list of bookmarks.
- private BookmarksListAdapter mListAdapter;
-
- // Adapter's parent stack.
- private List<FolderInfo> mSavedParentStack;
-
- // Reference to the View to display when there are no results.
- private View mEmptyView;
-
- // Callback for cursor loaders.
- private CursorLoaderCallbacks mLoaderCallbacks;
-
- @Override
- public void restoreData(@NonNull Bundle data) {
- final ArrayList<FolderInfo> stack = data.getParcelableArrayList("parentStack");
- if (stack == null) {
- return;
- }
-
- if (mListAdapter == null) {
- mSavedParentStack = new LinkedList<FolderInfo>(stack);
- } else {
- mListAdapter.restoreData(stack);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
-
- mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
- if (type == Bookmarks.TYPE_FOLDER) {
- // We don't show a context menu for folders
- return null;
- }
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
- info.bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
- info.itemType = RemoveItemType.BOOKMARKS;
- return info;
- }
- });
-
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- OnUrlOpenListener listener = null;
- try {
- listener = (OnUrlOpenListener) getActivity();
- } catch (ClassCastException e) {
- throw new ClassCastException(getActivity().toString()
- + " must implement HomePager.OnUrlOpenListener");
- }
-
- mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
- mList.setOnUrlOpenListener(listener);
-
- registerForContextMenu(mList);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Activity activity = getActivity();
-
- // Setup the list adapter.
- mListAdapter = new BookmarksListAdapter(activity, null, mSavedParentStack);
- mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() {
- @Override
- public void onRefreshFolder(FolderInfo folderInfo, RefreshType refreshType) {
- // Restart the loader with folder as the argument.
- Bundle bundle = new Bundle();
- bundle.putParcelable(BOOKMARKS_FOLDER_INFO, folderInfo);
- bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, refreshType);
- getLoaderManager().restartLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
- }
- });
- mList.setAdapter(mListAdapter);
-
- // Create callbacks before the initial loader is started.
- mLoaderCallbacks = new CursorLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void onDestroyView() {
- mList = null;
- mListAdapter = null;
- mEmptyView = null;
- super.onDestroyView();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- if (isVisible()) {
- // The parent stack is saved just so that the folder state can be
- // restored on rotation.
- mSavedParentStack = mListAdapter.getParentStack();
- }
- }
-
- @Override
- protected void load() {
- final Bundle bundle;
- if (mSavedParentStack != null && mSavedParentStack.size() > 1) {
- bundle = new Bundle();
- bundle.putParcelable(BOOKMARKS_FOLDER_INFO, mSavedParentStack.get(0));
- bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, RefreshType.CHILD);
- } else {
- bundle = null;
- }
-
- getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
- }
-
- private void updateUiFromCursor(Cursor c) {
- if ((c == null || c.getCount() == 0) && mEmptyView == null) {
- // Set empty page view. We delay this so that the empty view won't flash.
- final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
- mEmptyView = emptyViewStub.inflate();
-
- final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
- emptyIcon.setImageResource(R.drawable.icon_bookmarks_empty);
-
- final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
- emptyText.setText(R.string.home_bookmarks_empty);
-
- mList.setEmptyView(mEmptyView);
- }
- }
-
- /**
- * Loader for the list for bookmarks.
- */
- private static class BookmarksLoader extends SimpleCursorLoader {
- private final FolderInfo mFolderInfo;
- private final RefreshType mRefreshType;
- private final BrowserDB mDB;
-
- public BookmarksLoader(Context context) {
- this(context,
- new FolderInfo(Bookmarks.FIXED_ROOT_ID, context.getResources().getString(R.string.bookmarks_title)),
- RefreshType.CHILD);
- }
-
- public BookmarksLoader(Context context, FolderInfo folderInfo, RefreshType refreshType) {
- super(context);
- mFolderInfo = folderInfo;
- mRefreshType = refreshType;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final boolean isRootFolder = mFolderInfo.id == BrowserContract.Bookmarks.FIXED_ROOT_ID;
-
- final ContentResolver contentResolver = getContext().getContentResolver();
-
- Cursor partnerCursor = null;
- Cursor userCursor = null;
-
- if (GeckoSharedPrefs.forProfile(getContext()).getBoolean(GeckoPreferences.PREFS_READ_PARTNER_BOOKMARKS_PROVIDER, false)
- && (isRootFolder || mFolderInfo.id <= Bookmarks.FAKE_PARTNER_BOOKMARKS_START)) {
- partnerCursor = contentResolver.query(PartnerBookmarksProviderProxy.getUriForBookmarks(getContext(), mFolderInfo.id), null, null, null, null, null);
- }
-
- if (isRootFolder || mFolderInfo.id > Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
- userCursor = mDB.getBookmarksInFolder(contentResolver, mFolderInfo.id);
- }
-
-
- if (partnerCursor == null && userCursor == null) {
- return null;
- } else if (partnerCursor == null) {
- return userCursor;
- } else if (userCursor == null) {
- return partnerCursor;
- } else {
- return new MergeCursor(new Cursor[] { partnerCursor, userCursor });
- }
- }
-
- @Override
- public void onContentChanged() {
- // Invalidate the cached value that keeps track of whether or
- // not desktop bookmarks exist.
- mDB.invalidate();
- super.onContentChanged();
- }
-
- public FolderInfo getFolderInfo() {
- return mFolderInfo;
- }
-
- public RefreshType getRefreshType() {
- return mRefreshType;
- }
- }
-
- /**
- * Loader callbacks for the LoaderManager of this fragment.
- */
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (args == null) {
- return new BookmarksLoader(getActivity());
- } else {
- FolderInfo folderInfo = (FolderInfo) args.getParcelable(BOOKMARKS_FOLDER_INFO);
- RefreshType refreshType = (RefreshType) args.getParcelable(BOOKMARKS_REFRESH_TYPE);
- return new BookmarksLoader(getActivity(), folderInfo, refreshType);
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- BookmarksLoader bl = (BookmarksLoader) loader;
- mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType());
-
- if (mPanelStateChangeListener != null) {
- final List<FolderInfo> parentStack = mListAdapter.getParentStack();
- final Bundle bundle = new Bundle();
-
- // Bundle likes to store ArrayLists or Arrays, but we've got a generic List (which
- // is actually an unmodifiable wrapper around a LinkedList). We'll need to do a
- // LinkedList conversion at the other end, when saving we need to use this awkward
- // syntax to create an Array.
- bundle.putParcelableArrayList("parentStack", new ArrayList<FolderInfo>(parentStack));
-
- mPanelStateChangeListener.onStateChanged(bundle);
- }
- updateUiFromCursor(c);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mList != null) {
- mListAdapter.swapCursor(null);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
deleted file mode 100644
index 7732932fe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ /dev/null
@@ -1,1316 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-
-import android.content.SharedPreferences;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SuggestClient;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.History;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.SearchLoader.SearchCursorLoader;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.toolbar.AutocompleteHandler;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.AsyncTaskLoader;
-import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.WindowManager.LayoutParams;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.TranslateAnimation;
-import android.widget.AdapterView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Fragment that displays frecency search results in a ListView.
- */
-public class BrowserSearch extends HomeFragment
- implements GeckoEventListener,
- SearchEngineBar.OnSearchBarClickListener {
-
- @RobocopTarget
- public interface SuggestClientFactory {
- public SuggestClient getSuggestClient(Context context, String template, int timeout, int max);
- }
-
- @RobocopTarget
- public static class DefaultSuggestClientFactory implements SuggestClientFactory {
- @Override
- public SuggestClient getSuggestClient(Context context, String template, int timeout, int max) {
- return new SuggestClient(context, template, timeout, max, true);
- }
- }
-
- /**
- * Set this to mock the suggestion mechanism. Public for access from tests.
- */
- @RobocopTarget
- public static volatile SuggestClientFactory sSuggestClientFactory = new DefaultSuggestClientFactory();
-
- // Logging tag name
- private static final String LOGTAG = "GeckoBrowserSearch";
-
- // Cursor loader ID for search query
- private static final int LOADER_ID_SEARCH = 0;
-
- // AsyncTask loader ID for suggestion query
- private static final int LOADER_ID_SUGGESTION = 1;
- private static final int LOADER_ID_SAVED_SUGGESTION = 2;
-
- // Timeout for the suggestion client to respond
- private static final int SUGGESTION_TIMEOUT = 3000;
-
- // Maximum number of suggestions from the search engine's suggestion client. This impacts network traffic and device
- // data consumption whereas R.integer.max_saved_suggestions controls how many suggestion to show in the UI.
- private static final int NETWORK_SUGGESTION_MAX = 3;
-
- // The maximum number of rows deep in a search we'll dig
- // for an autocomplete result
- private static final int MAX_AUTOCOMPLETE_SEARCH = 20;
-
- // Length of https:// + 1 required to make autocomplete
- // fill in the domain, for both http:// and https://
- private static final int HTTPS_PREFIX_LENGTH = 9;
-
- // Duration for fade-in animation
- private static final int ANIMATION_DURATION = 250;
-
- // Holds the current search term to use in the query
- private volatile String mSearchTerm;
-
- // Adapter for the list of search results
- private SearchAdapter mAdapter;
-
- // The view shown by the fragment
- private LinearLayout mView;
-
- // The list showing search results
- private HomeListView mList;
-
- // The bar on the bottom of the screen displaying search engine options.
- private SearchEngineBar mSearchEngineBar;
-
- // Client that performs search suggestion queries.
- // Public for testing.
- @RobocopTarget
- public volatile SuggestClient mSuggestClient;
-
- // List of search engines from Gecko.
- // Do not mutate this list.
- // Access to this member must only occur from the UI thread.
- private List<SearchEngine> mSearchEngines;
-
- // Search history suggestions
- private ArrayList<String> mSearchHistorySuggestions;
-
- // Track the locale that was last in use when we filled mSearchEngines.
- // Access to this member must only occur from the UI thread.
- private Locale mLastLocale;
-
- // Whether search suggestions are enabled or not
- private boolean mSuggestionsEnabled;
-
- // Whether history suggestions are enabled or not
- private boolean mSavedSearchesEnabled;
-
- // Callbacks used for the search loader
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- // Callbacks used for the search suggestion loader
- private SearchEngineSuggestionLoaderCallbacks mSearchEngineSuggestionLoaderCallbacks;
- private SearchHistorySuggestionLoaderCallbacks mSearchHistorySuggestionLoaderCallback;
-
- // Autocomplete handler used when filtering results
- private AutocompleteHandler mAutocompleteHandler;
-
- // On search listener
- private OnSearchListener mSearchListener;
-
- // On edit suggestion listener
- private OnEditSuggestionListener mEditSuggestionListener;
-
- // Whether the suggestions will fade in when shown
- private boolean mAnimateSuggestions;
-
- // Opt-in prompt view for search suggestions
- private View mSuggestionsOptInPrompt;
-
- public interface OnSearchListener {
- void onSearch(SearchEngine engine, String text, TelemetryContract.Method method);
- }
-
- public interface OnEditSuggestionListener {
- public void onEditSuggestion(String suggestion);
- }
-
- public static BrowserSearch newInstance() {
- BrowserSearch browserSearch = new BrowserSearch();
-
- final Bundle args = new Bundle();
- args.putBoolean(HomePager.CAN_LOAD_ARG, true);
- browserSearch.setArguments(args);
-
- return browserSearch;
- }
-
- public BrowserSearch() {
- mSearchTerm = "";
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- mSearchListener = (OnSearchListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement BrowserSearch.OnSearchListener");
- }
-
- try {
- mEditSuggestionListener = (OnEditSuggestionListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement BrowserSearch.OnEditSuggestionListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
-
- mAutocompleteHandler = null;
- mSearchListener = null;
- mEditSuggestionListener = null;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mSearchEngines = new ArrayList<SearchEngine>();
- mSearchHistorySuggestions = new ArrayList<>();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- mSearchEngines = null;
- }
-
- @Override
- public void onHiddenChanged(boolean hidden) {
- if (!hidden) {
- final Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // Removes Search Suggestions Loader if in private browsing mode
- // Loader may have been inserted when browsing in normal tab
- if (isPrivate) {
- getLoaderManager().destroyLoader(LOADER_ID_SUGGESTION);
- }
-
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- }
- super.onHiddenChanged(hidden);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- mSavedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
-
- // Fetch engines if we need to.
- if (mSearchEngines.isEmpty() || !Locale.getDefault().equals(mLastLocale)) {
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- } else {
- updateSearchEngineBar();
- }
-
- Telemetry.startUISession(TelemetryContract.Session.FRECENCY);
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- Telemetry.stopUISession(TelemetryContract.Session.FRECENCY);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // All list views are styled to look the same with a global activity theme.
- // If the style of the list changes, inflate it from an XML.
- mView = (LinearLayout) inflater.inflate(R.layout.browser_search, container, false);
- mList = (HomeListView) mView.findViewById(R.id.home_list_view);
- mSearchEngineBar = (SearchEngineBar) mView.findViewById(R.id.search_engine_bar);
-
- return mView;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "SearchEngines:Data");
-
- mSearchEngineBar.setAdapter(null);
- mSearchEngineBar = null;
-
- mList.setAdapter(null);
- mList = null;
-
- mView = null;
- mSuggestionsOptInPrompt = null;
- mSuggestClient = null;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mList.setTag(HomePager.LIST_TAG_BROWSER_SEARCH);
-
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- // Perform the user-entered search if the user clicks on a search engine row.
- // This row will be disabled if suggestions (in addition to the user-entered term) are showing.
- if (view instanceof SearchEngineRow) {
- ((SearchEngineRow) view).performUserEnteredSearch();
- return;
- }
-
- // Account for the search engine rows.
- position -= getPrimaryEngineCount();
- final Cursor c = mAdapter.getCursor(position);
- final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "frecency");
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- });
-
- mList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Don't do anything when the user long-clicks on a search engine row.
- if (view instanceof SearchEngineRow) {
- return true;
- }
-
- // Account for the search engine rows.
- position -= getPrimaryEngineCount();
- return mList.onItemLongClick(parent, view, position, id);
- }
- });
-
- final ListSelectionListener listener = new ListSelectionListener();
- mList.setOnItemSelectedListener(listener);
- mList.setOnFocusChangeListener(listener);
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
-
- int bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID));
- info.bookmarkId = bookmarkId;
-
- int historyId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- info.historyId = historyId;
-
- boolean isBookmark = bookmarkId != -1;
- boolean isHistory = historyId != -1;
-
- if (isBookmark && isHistory) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.COMBINED;
- } else if (isBookmark) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.BOOKMARKS;
- } else if (isHistory) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.HISTORY;
- }
-
- return info;
- }
- });
-
- mList.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
- final View selected = mList.getSelectedView();
-
- if (selected instanceof SearchEngineRow) {
- return selected.onKeyDown(keyCode, event);
- }
- return false;
- }
- });
-
- registerForContextMenu(mList);
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "SearchEngines:Data");
-
- mSearchEngineBar.setOnSearchBarClickListener(this);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return;
- }
-
- HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
-
- MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.browsersearch_contextmenu, menu);
-
- menu.setHeaderTitle(info.getDisplayTitle());
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return false;
- }
-
- final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
- final Context context = getActivity();
-
- final int itemId = item.getItemId();
-
- if (itemId == R.id.browsersearch_remove) {
- // Position for Top Sites grid items, but will always be -1 since this is only for BrowserSearch result
- final int position = -1;
-
- new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
- return true;
- }
-
- return false;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // Initialize the search adapter
- mAdapter = new SearchAdapter(getActivity());
- mList.setAdapter(mAdapter);
-
- // Only create an instance when we need it
- mSearchEngineSuggestionLoaderCallbacks = null;
- mSearchHistorySuggestionLoaderCallback = null;
-
- // Create callbacks before the initial loader is started
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void handleMessage(String event, final JSONObject message) {
- if (event.equals("SearchEngines:Data")) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setSearchEngines(message);
- }
- });
- }
- }
-
- @Override
- protected void load() {
- SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- private void handleAutocomplete(String searchTerm, Cursor c) {
- if (c == null ||
- mAutocompleteHandler == null ||
- TextUtils.isEmpty(searchTerm)) {
- return;
- }
-
- // Avoid searching the path if we don't have to. Currently just
- // decided by whether there is a '/' character in the string.
- final boolean searchPath = searchTerm.indexOf('/') > 0;
- final String autocompletion = findAutocompletion(searchTerm, c, searchPath);
-
- if (autocompletion == null || mAutocompleteHandler == null) {
- return;
- }
-
- // Prefetch auto-completed domain since it's a likely target
- GeckoAppShell.notifyObservers("Session:Prefetch", "http://" + autocompletion);
-
- mAutocompleteHandler.onAutocomplete(autocompletion);
- mAutocompleteHandler = null;
- }
-
- /**
- * Returns the substring of a provided URI, starting at the given offset,
- * and extending up to the end of the path segment in which the provided
- * index is found.
- *
- * For example, given
- *
- * "www.reddit.com/r/boop/abcdef", 0, ?
- *
- * this method returns
- *
- * ?=2: "www.reddit.com/"
- * ?=17: "www.reddit.com/r/boop/"
- * ?=21: "www.reddit.com/r/boop/"
- * ?=22: "www.reddit.com/r/boop/abcdef"
- *
- */
- private static String uriSubstringUpToMatchedPath(final String url, final int offset, final int begin) {
- final int afterEnd = url.length();
-
- // We want to include the trailing slash, but not other characters.
- int chop = url.indexOf('/', begin);
- if (chop != -1) {
- ++chop;
- if (chop < offset) {
- // This isn't supposed to happen. Fall back to returning the whole damn thing.
- return url;
- }
- } else {
- chop = url.indexOf('?', begin);
- if (chop == -1) {
- chop = url.indexOf('#', begin);
- }
- if (chop == -1) {
- chop = afterEnd;
- }
- }
-
- return url.substring(offset, chop);
- }
-
- LinkedHashSet<String> domains = null;
- private LinkedHashSet<String> getDomains() {
- if (domains == null) {
- domains = new LinkedHashSet<String>(500);
- BufferedReader buf = null;
- try {
- buf = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.topdomains)));
- String res = null;
-
- do {
- res = buf.readLine();
- if (res != null) {
- domains.add(res);
- }
- } while (res != null);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error reading domains", e);
- } finally {
- if (buf != null) {
- try {
- buf.close();
- } catch (IOException e) { }
- }
- }
- }
- return domains;
- }
-
- private String searchDomains(String search) {
- for (String domain : getDomains()) {
- if (domain.startsWith(search)) {
- return domain;
- }
- }
- return null;
- }
-
- private String findAutocompletion(String searchTerm, Cursor c, boolean searchPath) {
- if (!c.moveToFirst()) {
- // No cursor probably means no history, so let's try the fallback list.
- return searchDomains(searchTerm);
- }
-
- final int searchLength = searchTerm.length();
- final int urlIndex = c.getColumnIndexOrThrow(History.URL);
- int searchCount = 0;
-
- do {
- final String url = c.getString(urlIndex);
-
- if (searchCount == 0) {
- // Prefetch the first item in the list since it's weighted the highest
- GeckoAppShell.notifyObservers("Session:Prefetch", url);
- }
-
- // Does the completion match against the whole URL? This will match
- // about: pages, as well as user input including "http://...".
- if (url.startsWith(searchTerm)) {
- return uriSubstringUpToMatchedPath(url, 0,
- (searchLength > HTTPS_PREFIX_LENGTH) ? searchLength : HTTPS_PREFIX_LENGTH);
- }
-
- final Uri uri = Uri.parse(url);
- final String host = uri.getHost();
-
- // Host may be null for about pages.
- if (host == null) {
- continue;
- }
-
- if (host.startsWith(searchTerm)) {
- return host + "/";
- }
-
- final String strippedHost = StringUtils.stripCommonSubdomains(host);
- if (strippedHost.startsWith(searchTerm)) {
- return strippedHost + "/";
- }
-
- ++searchCount;
-
- if (!searchPath) {
- continue;
- }
-
- // Otherwise, if we're matching paths, let's compare against the string itself.
- final int hostOffset = url.indexOf(strippedHost);
- if (hostOffset == -1) {
- // This was a URL string that parsed to a different host (normalized?).
- // Give up.
- continue;
- }
-
- // We already matched the non-stripped host, so now we're
- // substring-searching in the part of the URL without the common
- // subdomains.
- if (url.startsWith(searchTerm, hostOffset)) {
- // Great! Return including the rest of the path segment.
- return uriSubstringUpToMatchedPath(url, hostOffset, hostOffset + searchLength);
- }
- } while (searchCount < MAX_AUTOCOMPLETE_SEARCH && c.moveToNext());
-
- // If we can't find an autocompletion domain from history, let's try using the fallback list.
- return searchDomains(searchTerm);
- }
-
- public void resetScrollState() {
- mSearchEngineBar.scrollToPosition(0);
- }
-
- private void filterSuggestions() {
- Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // mSuggestClient may be null if we haven't received our search engine list yet - hence
- // we need to exit here in that case.
- if (isPrivate || mSuggestClient == null || (!mSuggestionsEnabled && !mSavedSearchesEnabled)) {
- mSearchHistorySuggestions.clear();
- return;
- }
-
- // Suggestions from search engine
- if (mSearchEngineSuggestionLoaderCallbacks == null) {
- mSearchEngineSuggestionLoaderCallbacks = new SearchEngineSuggestionLoaderCallbacks();
- }
- getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSearchEngineSuggestionLoaderCallbacks);
-
- // Saved suggestions
- if (mSearchHistorySuggestionLoaderCallback == null) {
- mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
- }
- getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
- }
-
- private void setSuggestions(ArrayList<String> suggestions) {
- ThreadUtils.assertOnUiThread();
-
- // mSearchEngines may be null if the setSuggestions calls after onDestroy (bug 1310621).
- // So drop the suggestions if search engines are not available
- if (mSearchEngines != null && !mSearchEngines.isEmpty()) {
- mSearchEngines.get(0).setSuggestions(suggestions);
- mAdapter.notifyDataSetChanged();
- }
-
- }
-
- private void setSavedSuggestions(ArrayList<String> savedSuggestions) {
- ThreadUtils.assertOnUiThread();
-
- mSearchHistorySuggestions = savedSuggestions;
- mAdapter.notifyDataSetChanged();
- }
-
- private boolean shouldUpdateSearchEngine(ArrayList<SearchEngine> searchEngines) {
- if (searchEngines.size() != mSearchEngines.size()) {
- return true;
- }
-
- int size = searchEngines.size();
-
- for (int i = 0; i < size; i++) {
- if (!mSearchEngines.get(i).name.equals(searchEngines.get(i).name)) {
- return true;
- }
- }
-
- return false;
- }
-
- private void setSearchEngines(JSONObject data) {
- ThreadUtils.assertOnUiThread();
-
- // This method is called via a Runnable posted from the Gecko thread, so
- // it's possible the fragment and/or its view has been destroyed by the
- // time we get here. If so, just abort.
- if (mView == null) {
- return;
- }
-
- try {
- final JSONObject suggest = data.getJSONObject("suggest");
- final String suggestEngine = suggest.optString("engine", null);
- final String suggestTemplate = suggest.optString("template", null);
- final boolean suggestionsPrompted = suggest.getBoolean("prompted");
- final JSONArray engines = data.getJSONArray("searchEngines");
-
- mSuggestionsEnabled = suggest.getBoolean("enabled");
-
- ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
- for (int i = 0; i < engines.length(); i++) {
- final JSONObject engineJSON = engines.getJSONObject(i);
- final SearchEngine engine = new SearchEngine((Context) getActivity(), engineJSON);
-
- if (engine.name.equals(suggestEngine) && suggestTemplate != null) {
- // Suggest engine should be at the front of the list.
- // We're baking in an assumption here that the suggest engine
- // is also the default engine.
- searchEngines.add(0, engine);
-
- ensureSuggestClientIsSet(suggestTemplate);
- } else {
- searchEngines.add(engine);
- }
- }
-
- // checking if the new searchEngine is different from mSearchEngine, will have to re-layout if yes
- boolean change = shouldUpdateSearchEngine(searchEngines);
-
- if (mAdapter != null && change) {
- mSearchEngines = Collections.unmodifiableList(searchEngines);
- mLastLocale = Locale.getDefault();
- updateSearchEngineBar();
-
- mAdapter.notifyDataSetChanged();
- }
-
- final Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // Show suggestions opt-in prompt only if suggestions are not enabled yet,
- // user hasn't been prompted and we're not on a private browsing tab.
- // The prompt might have been inflated already when this view was previously called.
- // Remove the opt-in prompt if it has been inflated in the view and dealt with by the user,
- // or if we're on a private browsing tab
- if (!mSuggestionsEnabled && !suggestionsPrompted && !isPrivate) {
- showSuggestionsOptIn();
- } else {
- removeSuggestionsOptIn();
- }
-
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error getting search engine JSON", e);
- }
-
- filterSuggestions();
- }
-
- private void updateSearchEngineBar() {
- final int primaryEngineCount = getPrimaryEngineCount();
-
- if (primaryEngineCount < mSearchEngines.size()) {
- mSearchEngineBar.setSearchEngines(
- mSearchEngines.subList(primaryEngineCount, mSearchEngines.size())
- );
- mSearchEngineBar.setVisibility(View.VISIBLE);
- } else {
- mSearchEngineBar.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onSearchBarClickListener(final SearchEngine searchEngine) {
- final TelemetryContract.Method method = TelemetryContract.Method.LIST_ITEM;
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method, "searchenginebar");
- mSearchListener.onSearch(searchEngine, mSearchTerm, method);
- }
-
- private void ensureSuggestClientIsSet(final String suggestTemplate) {
- // Don't update the suggestClient if we already have a client with the correct template
- if (mSuggestClient != null && suggestTemplate.equals(mSuggestClient.getSuggestTemplate())) {
- return;
- }
-
- mSuggestClient = sSuggestClientFactory.getSuggestClient(getActivity(), suggestTemplate, SUGGESTION_TIMEOUT, NETWORK_SUGGESTION_MAX);
- }
-
- private void showSuggestionsOptIn() {
- // Only make the ViewStub visible again if it has already previously been shown.
- // (An inflated ViewStub is removed from the View hierarchy so a second call to findViewById will return null,
- // which also further necessitates handling this separately.)
- if (mSuggestionsOptInPrompt != null) {
- mSuggestionsOptInPrompt.setVisibility(View.VISIBLE);
- return;
- }
-
- mSuggestionsOptInPrompt = ((ViewStub) mView.findViewById(R.id.suggestions_opt_in_prompt)).inflate();
-
- TextView promptText = (TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title);
- promptText.setText(getResources().getString(R.string.suggestions_prompt));
-
- final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes);
- final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no);
-
- final OnClickListener listener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- // Prevent the buttons from being clicked multiple times (bug 816902)
- yesButton.setOnClickListener(null);
- noButton.setOnClickListener(null);
-
- setSuggestionsEnabled(v == yesButton);
- }
- };
-
- yesButton.setOnClickListener(listener);
- noButton.setOnClickListener(listener);
-
- // If the prompt gains focus, automatically pass focus to the
- // yes button in the prompt.
- final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
- prompt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- yesButton.requestFocus();
- }
- }
- });
- }
-
- private void removeSuggestionsOptIn() {
- if (mSuggestionsOptInPrompt == null) {
- return;
- }
-
- mSuggestionsOptInPrompt.setVisibility(View.GONE);
- }
-
- private void setSuggestionsEnabled(final boolean enabled) {
- // Clicking the yes/no buttons quickly can cause the click events be
- // queued before the listeners are removed above, so it's possible
- // setSuggestionsEnabled() can be called twice. mSuggestionsOptInPrompt
- // can be null if this happens (bug 828480).
- if (mSuggestionsOptInPrompt == null) {
- return;
- }
-
- // Make suggestions appear immediately after the user opts in
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- SuggestClient client = mSuggestClient;
- if (client != null) {
- client.query(mSearchTerm);
- }
- }
- });
-
- PrefsHelper.setPref("browser.search.suggest.prompted", true);
- PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, (enabled ? "suggestions_optin_yes" : "suggestions_optin_no"));
-
- TranslateAnimation slideAnimation = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0);
- slideAnimation.setDuration(ANIMATION_DURATION);
- slideAnimation.setInterpolator(new AccelerateInterpolator());
- slideAnimation.setFillAfter(true);
- final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
-
- TranslateAnimation shrinkAnimation = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight());
- shrinkAnimation.setDuration(ANIMATION_DURATION);
- shrinkAnimation.setFillAfter(true);
- shrinkAnimation.setStartOffset(slideAnimation.getDuration());
- shrinkAnimation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation a) {
- // Increase the height of the view so a gap isn't shown during animation
- mView.getLayoutParams().height = mView.getHeight() +
- mSuggestionsOptInPrompt.getHeight();
- mView.requestLayout();
- }
-
- @Override
- public void onAnimationRepeat(Animation a) {}
-
- @Override
- public void onAnimationEnd(Animation a) {
- // Removing the view immediately results in a NPE in
- // dispatchDraw(), possibly because this callback executes
- // before drawing is finished. Posting this as a Runnable fixes
- // the issue.
- mView.post(new Runnable() {
- @Override
- public void run() {
- mView.removeView(mSuggestionsOptInPrompt);
- mList.clearAnimation();
- mSuggestionsOptInPrompt = null;
-
- // Reset the view height
- mView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
-
- // Show search suggestions and update them
- if (enabled) {
- mSuggestionsEnabled = enabled;
- mAnimateSuggestions = true;
- mAdapter.notifyDataSetChanged();
- filterSuggestions();
- }
- }
- });
- }
- });
-
- prompt.startAnimation(slideAnimation);
- mSuggestionsOptInPrompt.startAnimation(shrinkAnimation);
- mList.startAnimation(shrinkAnimation);
- }
-
- private int getPrimaryEngineCount() {
- return mSearchEngines.size() > 0 ? 1 : 0;
- }
-
- private void restartSearchLoader() {
- SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- private void initSearchLoader() {
- SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- public void filter(String searchTerm, AutocompleteHandler handler) {
- if (TextUtils.isEmpty(searchTerm)) {
- return;
- }
-
- final boolean isNewFilter = !TextUtils.equals(mSearchTerm, searchTerm);
-
- mSearchTerm = searchTerm;
- mAutocompleteHandler = handler;
-
- if (mAdapter != null) {
- if (isNewFilter) {
- // The adapter depends on the search term to determine its number
- // of items. Make it we notify the view about it.
- mAdapter.notifyDataSetChanged();
-
- // Restart loaders with the new search term
- restartSearchLoader();
- filterSuggestions();
- } else {
- // The search term hasn't changed, simply reuse any existing
- // loader for the current search term. This will ensure autocompletion
- // is consistently triggered (see bug 933739).
- initSearchLoader();
- }
- }
- }
-
- abstract private static class SuggestionAsyncLoader extends AsyncTaskLoader<ArrayList<String>> {
- protected final String mSearchTerm;
- private ArrayList<String> mSuggestions;
-
- public SuggestionAsyncLoader(Context context, String searchTerm) {
- super(context);
- mSearchTerm = searchTerm;
- }
-
- @Override
- public void deliverResult(ArrayList<String> suggestions) {
- mSuggestions = suggestions;
-
- if (isStarted()) {
- super.deliverResult(mSuggestions);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mSuggestions != null) {
- deliverResult(mSuggestions);
- }
-
- if (takeContentChanged() || mSuggestions == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- onStopLoading();
- mSuggestions = null;
- }
- }
-
- private static class SearchEngineSuggestionAsyncLoader extends SuggestionAsyncLoader {
- private final SuggestClient mSuggestClient;
-
- public SearchEngineSuggestionAsyncLoader(Context context, SuggestClient suggestClient, String searchTerm) {
- super(context, searchTerm);
- mSuggestClient = suggestClient;
- }
-
- @Override
- public ArrayList<String> loadInBackground() {
- return mSuggestClient.query(mSearchTerm);
- }
- }
-
- private static class SearchHistorySuggestionAsyncLoader extends SuggestionAsyncLoader {
- public SearchHistorySuggestionAsyncLoader(Context context, String searchTerm) {
- super(context, searchTerm);
- }
-
- @Override
- public ArrayList<String> loadInBackground() {
- final ContentResolver cr = getContext().getContentResolver();
-
- String[] columns = new String[] { BrowserContract.SearchHistory.QUERY };
- String actualQuery = BrowserContract.SearchHistory.QUERY + " LIKE ?";
- String[] queryArgs = new String[] { '%' + mSearchTerm + '%' };
-
- // For deduplication, the worst case is that all the first NETWORK_SUGGESTION_MAX history suggestions are duplicates
- // of search engine suggestions, and the there is a duplicate for the search term itself. A duplicate of the
- // search term can occur if the user has previously searched for the same thing.
- final int maxSavedSuggestions = NETWORK_SUGGESTION_MAX + 1 + getContext().getResources().getInteger(R.integer.max_saved_suggestions);
-
- final String sortOrderAndLimit = BrowserContract.SearchHistory.DATE + " DESC LIMIT " + maxSavedSuggestions;
- final Cursor result = cr.query(BrowserContract.SearchHistory.CONTENT_URI, columns, actualQuery, queryArgs, sortOrderAndLimit);
-
- if (result == null) {
- return new ArrayList<>();
- }
-
- final ArrayList<String> savedSuggestions = new ArrayList<>();
- try {
- if (result.moveToFirst()) {
- final int searchColumn = result.getColumnIndexOrThrow(BrowserContract.SearchHistory.QUERY);
- do {
- final String savedSearch = result.getString(searchColumn);
- savedSuggestions.add(savedSearch);
- } while (result.moveToNext());
- }
- } finally {
- result.close();
- }
-
- return savedSuggestions;
- }
- }
-
- private class SearchAdapter extends MultiTypeCursorAdapter {
- private static final int ROW_SEARCH = 0;
- private static final int ROW_STANDARD = 1;
- private static final int ROW_SUGGEST = 2;
-
- public SearchAdapter(Context context) {
- super(context, null, new int[] { ROW_STANDARD,
- ROW_SEARCH,
- ROW_SUGGEST },
- new int[] { R.layout.home_item_row,
- R.layout.home_search_item_row,
- R.layout.home_search_item_row });
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position < getPrimaryEngineCount()) {
- if (mSuggestionsEnabled && mSearchEngines.get(position).hasSuggestions()) {
- // Give suggestion views their own type to prevent them from
- // sharing other recycled search result views. Using other
- // recycled views for the suggestion row can break animations
- // (bug 815937).
-
- return ROW_SUGGEST;
- } else {
- return ROW_SEARCH;
- }
- }
-
- return ROW_STANDARD;
- }
-
- @Override
- public boolean isEnabled(int position) {
- // If we're using a gamepad or keyboard, allow the row to be
- // focused so it can pass the focus to its child suggestion views.
- if (!mList.isInTouchMode()) {
- return true;
- }
-
- // If the suggestion row only contains one item (the user-entered
- // query), allow the entire row to be clickable; clicking the row
- // has the same effect as clicking the single suggestion. If the
- // row contains multiple items, clicking the row will do nothing.
-
- if (position < getPrimaryEngineCount()) {
- return !mSearchEngines.get(position).hasSuggestions();
- }
-
- return true;
- }
-
- // Add the search engines to the number of reported results.
- @Override
- public int getCount() {
- final int resultCount = super.getCount();
-
- // Don't show search engines or suggestions if search field is empty
- if (TextUtils.isEmpty(mSearchTerm)) {
- return resultCount;
- }
-
- return resultCount + getPrimaryEngineCount();
- }
-
- @Override
- public void bindView(View view, Context context, int position) {
- final int type = getItemViewType(position);
-
- if (type == ROW_SEARCH || type == ROW_SUGGEST) {
- final SearchEngineRow row = (SearchEngineRow) view;
- row.setOnUrlOpenListener(mUrlOpenListener);
- row.setOnSearchListener(mSearchListener);
- row.setOnEditSuggestionListener(mEditSuggestionListener);
- row.setSearchTerm(mSearchTerm);
-
- final SearchEngine engine = mSearchEngines.get(position);
- final boolean haveSuggestions = (engine.hasSuggestions() || !mSearchHistorySuggestions.isEmpty());
- final boolean animate = (mAnimateSuggestions && haveSuggestions);
- row.updateSuggestions(mSuggestionsEnabled, engine, mSearchHistorySuggestions, animate);
- if (animate) {
- // Only animate suggestions the first time they are shown
- mAnimateSuggestions = false;
- }
- } else {
- // Account for the search engines
- position -= getPrimaryEngineCount();
-
- final Cursor c = getCursor(position);
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(c);
- }
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return SearchLoader.createInstance(getActivity(), args);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- if (mAdapter != null) {
- mAdapter.swapCursor(c);
-
- // We should handle autocompletion based on the search term
- // associated with the loader that has just provided
- // the results.
- SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
- handleAutocomplete(searchLoader.getSearchTerm(), c);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mAdapter != null) {
- mAdapter.swapCursor(null);
- }
- }
- }
-
- private class SearchEngineSuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
- @Override
- public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
- // mSuggestClient is set to null in onDestroyView(), so using it
- // safely here relies on the fact that onCreateLoader() is called
- // synchronously in restartLoader().
- return new SearchEngineSuggestionAsyncLoader(getActivity(), mSuggestClient, mSearchTerm);
- }
-
- @Override
- public void onLoadFinished(Loader<ArrayList<String>> loader, ArrayList<String> suggestions) {
- setSuggestions(suggestions);
- }
-
- @Override
- public void onLoaderReset(Loader<ArrayList<String>> loader) {
- setSuggestions(new ArrayList<String>());
- }
- }
-
- private class SearchHistorySuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
- @Override
- public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
- // mSuggestClient is set to null in onDestroyView(), so using it
- // safely here relies on the fact that onCreateLoader() is called
- // synchronously in restartLoader().
- return new SearchHistorySuggestionAsyncLoader(getActivity(), mSearchTerm);
- }
-
- @Override
- public void onLoadFinished(Loader<ArrayList<String>> loader, ArrayList<String> suggestions) {
- setSavedSuggestions(suggestions);
- }
-
- @Override
- public void onLoaderReset(Loader<ArrayList<String>> loader) {
- setSavedSuggestions(new ArrayList<String>());
- }
- }
-
- private static class ListSelectionListener implements View.OnFocusChangeListener,
- AdapterView.OnItemSelectedListener {
- private SearchEngineRow mSelectedEngineRow;
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- View selectedRow = ((ListView) v).getSelectedView();
- if (selectedRow != null) {
- selectRow(selectedRow);
- }
- } else {
- deselectRow();
- }
- }
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- deselectRow();
- selectRow(view);
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- deselectRow();
- }
-
- private void selectRow(View row) {
- if (row instanceof SearchEngineRow) {
- mSelectedEngineRow = (SearchEngineRow) row;
- mSelectedEngineRow.onSelected();
- }
- }
-
- private void deselectRow() {
- if (mSelectedEngineRow != null) {
- mSelectedEngineRow.onDeselected();
- mSelectedEngineRow = null;
- }
- }
- }
-
- /**
- * HomeSearchListView is a list view for displaying search engine results on the awesome screen.
- */
- public static class HomeSearchListView extends HomeListView {
- public HomeSearchListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.homeListViewStyle);
- }
-
- public HomeSearchListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Dismiss the soft keyboard.
- requestFocus();
- }
-
- return super.onTouchEvent(event);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java
deleted file mode 100644
index f288a2745..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.annotation.UiThread;
-import android.support.v4.util.Pair;
-import android.support.v7.widget.RecyclerView;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import static org.mozilla.gecko.home.CombinedHistoryItem.ItemType.*;
-
-public class ClientsAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
- public static final String LOGTAG = "GeckoClientsAdapter";
-
- /**
- * If a device claims to have synced before this date, we will assume it has never synced.
- */
- public static final Date EARLIEST_VALID_SYNCED_DATE;
- static {
- final Calendar c = GregorianCalendar.getInstance();
- c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
- EARLIEST_VALID_SYNCED_DATE = c.getTime();
- }
-
- List<Pair<String, Integer>> adapterList = new LinkedList<>();
-
- // List of hidden remote clients.
- // Only accessed from the UI thread.
- protected final List<RemoteClient> hiddenClients = new ArrayList<>();
- private Map<String, RemoteClient> visibleClients = new HashMap<>();
-
- // Maintain group collapsed and hidden state. Only accessed from the UI thread.
- protected static RemoteTabsExpandableListState sState;
-
- private final Context context;
-
- public ClientsAdapter(Context context) {
- this.context = context;
-
- // This races when multiple Fragments are created. That's okay: one
- // will win, and thereafter, all will be okay. If we create and then
- // drop an instance the shared SharedPreferences backing all the
- // instances will maintain the state for us. Since everything happens on
- // the UI thread, this doesn't even need to be volatile.
- if (sState == null) {
- sState = new RemoteTabsExpandableListState(GeckoSharedPrefs.forProfile(context));
- }
-
- this.setHasStableIds(true);
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case NAVIGATION_BACK:
- view = inflater.inflate(R.layout.home_combined_back_item, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case CLIENT:
- view = inflater.inflate(R.layout.home_remote_tabs_group, parent, false);
- return new CombinedHistoryItem.ClientItem(view);
-
- case CHILD:
- view = inflater.inflate(R.layout.home_item_row, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case HIDDEN_DEVICES:
- view = inflater.inflate(R.layout.home_remote_tabs_hidden_devices, parent, false);
- return new CombinedHistoryItem.BasicItem(view);
- }
- return null;
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem holder, final int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
-
- switch (itemType) {
- case CLIENT:
- final CombinedHistoryItem.ClientItem clientItem = (CombinedHistoryItem.ClientItem) holder;
- final String clientGuid = adapterList.get(position).first;
- final RemoteClient client = visibleClients.get(clientGuid);
- clientItem.bind(context, client, sState.isClientCollapsed(clientGuid));
- break;
-
- case CHILD:
- final Pair<String, Integer> pair = adapterList.get(position);
- RemoteTab remoteTab = visibleClients.get(pair.first).tabs.get(pair.second);
- ((CombinedHistoryItem.HistoryItem) holder).bind(remoteTab);
- break;
-
- case HIDDEN_DEVICES:
- final String hiddenDevicesLabel = context.getResources().getString(R.string.home_remote_tabs_many_hidden_devices, hiddenClients.size());
- ((TextView) holder.itemView).setText(hiddenDevicesLabel);
- break;
- }
- }
-
- @Override
- public int getItemCount () {
- return adapterList.size();
- }
-
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == 0) {
- return NAVIGATION_BACK;
- }
-
- final Pair<String, Integer> pair = adapterList.get(position);
- if (pair == null) {
- return HIDDEN_DEVICES;
- } else if (pair.second == -1) {
- return CLIENT;
- } else {
- return CHILD;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- @Override
- public long getItemId(int position) {
- // RecyclerView.NO_ID is -1, so start our hard-coded IDs at -2.
- final int NAVIGATION_BACK_ID = -2;
- final int HIDDEN_DEVICES_ID = -3;
-
- final String clientGuid;
- // adapterList is a list of tuples (clientGuid, tabId).
- final Pair<String, Integer> pair = adapterList.get(position);
-
- switch (getItemTypeForPosition(position)) {
- case NAVIGATION_BACK:
- return NAVIGATION_BACK_ID;
-
- case HIDDEN_DEVICES:
- return HIDDEN_DEVICES_ID;
-
- // For Clients, return hashCode of their GUIDs.
- case CLIENT:
- clientGuid = pair.first;
- return clientGuid.hashCode();
-
- // For Tabs, return hashCode of their URLs.
- case CHILD:
- clientGuid = pair.first;
- final Integer tabId = pair.second;
-
- final RemoteClient remoteClient = visibleClients.get(clientGuid);
- if (remoteClient == null) {
- return RecyclerView.NO_ID;
- }
-
- final RemoteTab remoteTab = remoteClient.tabs.get(tabId);
- if (remoteTab == null) {
- return RecyclerView.NO_ID;
- }
-
- return remoteTab.url.hashCode();
-
- default:
- throw new IllegalStateException("Unexpected Home Panel item type");
- }
- }
-
- public int getClientsCount() {
- return hiddenClients.size() + visibleClients.size();
- }
-
- @UiThread
- public void setClients(List<RemoteClient> clients) {
- adapterList.clear();
- adapterList.add(null);
-
- hiddenClients.clear();
- visibleClients.clear();
-
- for (RemoteClient client : clients) {
- final String guid = client.guid;
- if (sState.isClientHidden(guid)) {
- hiddenClients.add(client);
- } else {
- visibleClients.put(guid, client);
- adapterList.addAll(getVisibleItems(client));
- }
- }
-
- // Add item for unhiding clients.
- if (!hiddenClients.isEmpty()) {
- adapterList.add(null);
- }
-
- notifyDataSetChanged();
- }
-
- private static List<Pair<String, Integer>> getVisibleItems(RemoteClient client) {
- List<Pair<String, Integer>> list = new LinkedList<>();
- final String guid = client.guid;
- list.add(new Pair<>(guid, -1));
- if (!sState.isClientCollapsed(client.guid)) {
- for (int i = 0; i < client.tabs.size(); i++) {
- list.add(new Pair<>(guid, i));
- }
- }
- return list;
- }
-
- public List<RemoteClient> getHiddenClients() {
- return hiddenClients;
- }
-
- public void toggleClient(int position) {
- final Pair<String, Integer> pair = adapterList.get(position);
- if (pair.second != -1) {
- return;
- }
-
- final String clientGuid = pair.first;
- final RemoteClient client = visibleClients.get(clientGuid);
-
- final boolean isCollapsed = sState.isClientCollapsed(clientGuid);
-
- sState.setClientCollapsed(clientGuid, !isCollapsed);
- notifyItemChanged(position);
-
- if (isCollapsed) {
- for (int i = client.tabs.size() - 1; i > -1; i--) {
- // Insert child tabs at the index right after the client item that was clicked.
- adapterList.add(position + 1, new Pair<>(clientGuid, i));
- }
- notifyItemRangeInserted(position + 1, client.tabs.size());
- } else {
- int i = client.tabs.size();
- while (i > 0) {
- adapterList.remove(position + 1);
- i--;
- }
- notifyItemRangeRemoved(position + 1, client.tabs.size());
- }
- }
-
- public void unhideClients(List<RemoteClient> selectedClients) {
- final int numClients = selectedClients.size();
- if (numClients == 0) {
- return;
- }
-
- final int insertionIndex = adapterList.size() - 1;
- int itemCount = numClients;
-
- for (RemoteClient client : selectedClients) {
- final String clientGuid = client.guid;
-
- sState.setClientHidden(clientGuid, false);
- hiddenClients.remove(client);
-
- visibleClients.put(clientGuid, client);
- sState.setClientCollapsed(clientGuid, false);
- adapterList.addAll(adapterList.size() - 1, getVisibleItems(client));
-
- itemCount += client.tabs.size();
- }
-
- notifyItemRangeInserted(insertionIndex, itemCount);
-
- final int hiddenDevicesIndex = adapterList.size() - 1;
- if (hiddenClients.isEmpty()) {
- // No more hidden clients, remove "unhide" item.
- adapterList.remove(hiddenDevicesIndex);
- notifyItemRemoved(hiddenDevicesIndex);
- } else {
- // Update "hidden clients" item because number of hidden clients changed.
- notifyItemChanged(hiddenDevicesIndex);
- }
- }
-
- public void removeItem(int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- switch (itemType) {
- case CLIENT:
- final String clientGuid = adapterList.get(position).first;
- final RemoteClient client = visibleClients.remove(clientGuid);
- final boolean hadHiddenClients = !hiddenClients.isEmpty();
-
- int removeCount = sState.isClientCollapsed(clientGuid) ? 1 : client.tabs.size() + 1;
- int c = removeCount;
- while (c > 0) {
- adapterList.remove(position);
- c--;
- }
- notifyItemRangeRemoved(position, removeCount);
-
- sState.setClientHidden(clientGuid, true);
- hiddenClients.add(client);
-
- if (!hadHiddenClients) {
- // Add item for unhiding clients;
- adapterList.add(null);
- notifyItemInserted(adapterList.size() - 1);
- } else {
- // Update "hidden clients" item because number of hidden clients changed.
- notifyItemChanged(adapterList.size() - 1);
- }
- break;
- }
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- HomeContextMenuInfo info;
- final Pair<String, Integer> pair = adapterList.get(position);
- switch (itemType) {
- case CHILD:
- info = new HomeContextMenuInfo(view, position, -1);
- return populateChildInfoFromTab(info, visibleClients.get(pair.first).tabs.get(pair.second));
-
- case CLIENT:
- info = new CombinedHistoryPanel.RemoteTabsClientContextMenuInfo(view, position, -1, visibleClients.get(pair.first));
- return info;
- }
- return null;
- }
-
- protected static HomeContextMenuInfo populateChildInfoFromTab(HomeContextMenuInfo info, RemoteTab tab) {
- info.url = tab.url;
- info.title = tab.title;
- return info;
- }
-
- /**
- * Return a relative "Last synced" time span for the given tab record.
- *
- * @param now local time.
- * @param time to format string for.
- * @return string describing time span
- */
- public static String getLastSyncedString(Context context, long now, long time) {
- if (new Date(time).before(EARLIEST_VALID_SYNCED_DATE)) {
- return context.getString(R.string.remote_tabs_never_synced);
- }
- final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
- return context.getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
deleted file mode 100644
index 402ed26e7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home;
-
-import android.content.res.Resources;
-import android.support.annotation.UiThread;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-
-import android.database.Cursor;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.util.ThreadUtils;
-
-public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
- private static final int RECENT_TABS_SMARTFOLDER_INDEX = 0;
-
- // Array for the time ranges in milliseconds covered by each section.
- static final HistorySectionsHelper.SectionDateRange[] sectionDateRangeArray = new HistorySectionsHelper.SectionDateRange[SectionHeader.values().length];
-
- // Semantic names for the time covered by each section
- public enum SectionHeader {
- TODAY,
- YESTERDAY,
- WEEK,
- THIS_MONTH,
- MONTH_AGO,
- TWO_MONTHS_AGO,
- THREE_MONTHS_AGO,
- FOUR_MONTHS_AGO,
- FIVE_MONTHS_AGO,
- OLDER_THAN_SIX_MONTHS
- }
-
- private HomeFragment.PanelStateChangeListener panelStateChangeListener;
-
- private Cursor historyCursor;
- private DevicesUpdateHandler devicesUpdateHandler;
- private int deviceCount = 0;
- private RecentTabsUpdateHandler recentTabsUpdateHandler;
- private int recentTabsCount = 0;
-
- private LinearLayoutManager linearLayoutManager; // Only used on the UI thread, so no need to be volatile.
-
- // We use a sparse array to store each section header's position in the panel [more cheaply than a HashMap].
- private final SparseArray<SectionHeader> sectionHeaders;
-
- public CombinedHistoryAdapter(Resources resources, int cachedRecentTabsCount) {
- super();
- recentTabsCount = cachedRecentTabsCount;
- sectionHeaders = new SparseArray<>();
- HistorySectionsHelper.updateRecentSectionOffset(resources, sectionDateRangeArray);
- this.setHasStableIds(true);
- }
-
- public void setPanelStateChangeListener(
- HomeFragment.PanelStateChangeListener panelStateChangeListener) {
- this.panelStateChangeListener = panelStateChangeListener;
- }
-
- @UiThread
- public void setLinearLayoutManager(LinearLayoutManager linearLayoutManager) {
- this.linearLayoutManager = linearLayoutManager;
- }
-
- public void setHistory(Cursor history) {
- historyCursor = history;
- populateSectionHeaders(historyCursor, sectionHeaders);
- notifyDataSetChanged();
- }
-
- public interface DevicesUpdateHandler {
- void onDeviceCountUpdated(int count);
- }
-
- public DevicesUpdateHandler getDeviceUpdateHandler() {
- if (devicesUpdateHandler == null) {
- devicesUpdateHandler = new DevicesUpdateHandler() {
- @Override
- public void onDeviceCountUpdated(int count) {
- deviceCount = count;
- notifyItemChanged(getSyncedDevicesSmartFolderIndex());
- }
- };
- }
- return devicesUpdateHandler;
- }
-
- public interface RecentTabsUpdateHandler {
- void onRecentTabsCountUpdated(int count, boolean countReliable);
- }
-
- public RecentTabsUpdateHandler getRecentTabsUpdateHandler() {
- if (recentTabsUpdateHandler != null) {
- return recentTabsUpdateHandler;
- }
-
- recentTabsUpdateHandler = new RecentTabsUpdateHandler() {
- @Override
- public void onRecentTabsCountUpdated(final int count, final boolean countReliable) {
- // Now that other items can move around depending on the visibility of the
- // Recent Tabs folder, only update the recentTabsCount on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @UiThread
- @Override
- public void run() {
- if (!countReliable && count <= recentTabsCount) {
- // The final tab count (where countReliable = true) is normally >= than
- // previous values with countReliable = false. Hence we only want to
- // update the displayed tab count with a preliminary value if it's larger
- // than the previous count, so as to avoid the displayed count jumping
- // downwards and then back up, as well as unnecessary folder animations.
- return;
- }
-
- final boolean prevFolderVisibility = isRecentTabsFolderVisible();
- recentTabsCount = count;
- final boolean folderVisible = isRecentTabsFolderVisible();
-
- if (prevFolderVisibility == folderVisible) {
- if (prevFolderVisibility) {
- notifyItemChanged(RECENT_TABS_SMARTFOLDER_INDEX);
- }
- return;
- }
-
- // If the Recent Tabs smart folder has become hidden/unhidden,
- // we need to recalculate the history section header positions.
- populateSectionHeaders(historyCursor, sectionHeaders);
-
- if (folderVisible) {
- int scrollPos = -1;
- if (linearLayoutManager != null) {
- scrollPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
- }
-
- notifyItemInserted(RECENT_TABS_SMARTFOLDER_INDEX);
- // If the list exceeds the display height and we want to show the new
- // item inserted at position 0, we need to scroll up manually
- // (see https://code.google.com/p/android/issues/detail?id=174227#c2).
- // However we only do this if our current scroll position is at the
- // top of the list.
- if (linearLayoutManager != null && scrollPos == 0) {
- linearLayoutManager.scrollToPosition(0);
- }
- } else {
- notifyItemRemoved(RECENT_TABS_SMARTFOLDER_INDEX);
- }
-
- if (countReliable && panelStateChangeListener != null) {
- panelStateChangeListener.setCachedRecentTabsCount(recentTabsCount);
- }
- }
- });
- }
- };
- return recentTabsUpdateHandler;
- }
-
- @UiThread
- private boolean isRecentTabsFolderVisible() {
- return recentTabsCount > 0;
- }
-
- @UiThread
- // Number of smart folders for determining practical empty state.
- public int getNumVisibleSmartFolders() {
- int visibleFolders = 1; // Synced devices folder is always visible.
-
- if (isRecentTabsFolderVisible()) {
- visibleFolders += 1;
- }
-
- return visibleFolders;
- }
-
- @UiThread
- private int getSyncedDevicesSmartFolderIndex() {
- return isRecentTabsFolderVisible() ?
- RECENT_TABS_SMARTFOLDER_INDEX + 1 :
- RECENT_TABS_SMARTFOLDER_INDEX;
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case RECENT_TABS:
- case SYNCED_DEVICES:
- view = inflater.inflate(R.layout.home_smartfolder, viewGroup, false);
- return new CombinedHistoryItem.SmartFolder(view);
-
- case SECTION_HEADER:
- view = inflater.inflate(R.layout.home_header_row, viewGroup, false);
- return new CombinedHistoryItem.BasicItem(view);
-
- case HISTORY:
- view = inflater.inflate(R.layout.home_item_row, viewGroup, false);
- return new CombinedHistoryItem.HistoryItem(view);
- default:
- throw new IllegalArgumentException("Unexpected Home Panel item type");
- }
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- final int localPosition = transformAdapterPositionForDataStructure(itemType, position);
-
- switch (itemType) {
- case RECENT_TABS:
- ((CombinedHistoryItem.SmartFolder) viewHolder).bind(R.drawable.icon_recent, R.string.home_closed_tabs_title2, R.string.home_closed_tabs_one, R.string.home_closed_tabs_number, recentTabsCount);
- break;
-
- case SYNCED_DEVICES:
- ((CombinedHistoryItem.SmartFolder) viewHolder).bind(R.drawable.cloud, R.string.home_synced_devices_smartfolder, R.string.home_synced_devices_one, R.string.home_synced_devices_number, deviceCount);
- break;
-
- case SECTION_HEADER:
- ((TextView) viewHolder.itemView).setText(getSectionHeaderTitle(sectionHeaders.get(localPosition)));
- break;
-
- case HISTORY:
- if (historyCursor == null || !historyCursor.moveToPosition(localPosition)) {
- throw new IllegalStateException("Couldn't move cursor to position " + localPosition);
- }
- ((CombinedHistoryItem.HistoryItem) viewHolder).bind(historyCursor);
- break;
- }
- }
-
- /**
- * Transform an adapter position to the position for the data structure backing the item type.
- *
- * The type is not strictly necessary and could be fetched from <code>getItemTypeForPosition</code>,
- * but is used for explicitness.
- *
- * @param type ItemType of the item
- * @param position position in the adapter
- * @return position of the item in the data structure
- */
- @UiThread
- private int transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType type, int position) {
- if (type == CombinedHistoryItem.ItemType.SECTION_HEADER) {
- return position;
- } else if (type == CombinedHistoryItem.ItemType.HISTORY) {
- return position - getHeadersBefore(position) - getNumVisibleSmartFolders();
- } else {
- return position;
- }
- }
-
- @UiThread
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == RECENT_TABS_SMARTFOLDER_INDEX && isRecentTabsFolderVisible()) {
- return CombinedHistoryItem.ItemType.RECENT_TABS;
- }
- if (position == getSyncedDevicesSmartFolderIndex()) {
- return CombinedHistoryItem.ItemType.SYNCED_DEVICES;
- }
- final int sectionPosition = transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType.SECTION_HEADER, position);
- if (sectionHeaders.get(sectionPosition) != null) {
- return CombinedHistoryItem.ItemType.SECTION_HEADER;
- }
- return CombinedHistoryItem.ItemType.HISTORY;
- }
-
- @UiThread
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- @UiThread
- @Override
- public int getItemCount() {
- final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
- return historySize + sectionHeaders.size() + getNumVisibleSmartFolders();
- }
-
- /**
- * Returns stable ID for each position. Data behind historyCursor is a sorted Combined view.
- *
- * @param position view item position for which to generate a stable ID
- * @return stable ID for given position
- */
- @UiThread
- @Override
- public long getItemId(int position) {
- // Two randomly selected large primes used to generate non-clashing IDs.
- final long PRIME_BOOKMARKS = 32416189867L;
- final long PRIME_SECTION_HEADERS = 32416187737L;
-
- // RecyclerView.NO_ID is -1, so let's start from -2 for our hard-coded IDs.
- final int RECENT_TABS_ID = -2;
- final int SYNCED_DEVICES_ID = -3;
-
- switch (getItemTypeForPosition(position)) {
- case RECENT_TABS:
- return RECENT_TABS_ID;
- case SYNCED_DEVICES:
- return SYNCED_DEVICES_ID;
- case SECTION_HEADER:
- // We might have multiple section headers, so we try get unique IDs for them.
- return position * PRIME_SECTION_HEADERS;
- case HISTORY:
- final int historyPosition = transformAdapterPositionForDataStructure(
- CombinedHistoryItem.ItemType.HISTORY, position);
- if (!historyCursor.moveToPosition(historyPosition)) {
- return RecyclerView.NO_ID;
- }
-
- final int historyIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID);
- final long historyId = historyCursor.getLong(historyIdCol);
-
- if (historyId != -1) {
- return historyId;
- }
-
- final int bookmarkIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID);
- final long bookmarkId = historyCursor.getLong(bookmarkIdCol);
-
- // Avoid clashing with historyId.
- return bookmarkId * PRIME_BOOKMARKS;
- default:
- throw new IllegalStateException("Unexpected Home Panel item type");
- }
- }
-
- /**
- * Add only the SectionHeaders that have history items within their range to a SparseArray, where the
- * array index is the position of the header in the history-only (no clients) ordering.
- * @param c data Cursor
- * @param sparseArray SparseArray to populate
- */
- @UiThread
- private void populateSectionHeaders(Cursor c, SparseArray<SectionHeader> sparseArray) {
- ThreadUtils.assertOnUiThread();
-
- sparseArray.clear();
-
- if (c == null || !c.moveToFirst()) {
- return;
- }
-
- SectionHeader section = null;
-
- do {
- final int historyPosition = c.getPosition();
- final long visitTime = c.getLong(c.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
- final SectionHeader itemSection = getSectionFromTime(visitTime);
-
- if (section != itemSection) {
- section = itemSection;
- sparseArray.append(historyPosition + sparseArray.size() + getNumVisibleSmartFolders(), section);
- }
-
- if (section == SectionHeader.OLDER_THAN_SIX_MONTHS) {
- break;
- }
- } while (c.moveToNext());
- }
-
- private static String getSectionHeaderTitle(SectionHeader section) {
- return sectionDateRangeArray[section.ordinal()].displayName;
- }
-
- private static SectionHeader getSectionFromTime(long time) {
- for (int i = 0; i < SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
- if (time > sectionDateRangeArray[i].start) {
- return SectionHeader.values()[i];
- }
- }
-
- return SectionHeader.OLDER_THAN_SIX_MONTHS;
- }
-
- /**
- * Returns the number of section headers before the given history item at the adapter position.
- * @param position position in the adapter
- */
- private int getHeadersBefore(int position) {
- // Skip the first header case because there will always be a header.
- for (int i = 1; i < sectionHeaders.size(); i++) {
- // If the position of the header is greater than the history position,
- // return the number of headers tested.
- if (sectionHeaders.keyAt(i) > position) {
- return i;
- }
- }
- return sectionHeaders.size();
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- if (itemType == CombinedHistoryItem.ItemType.HISTORY) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, -1);
-
- historyCursor.moveToPosition(transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType.HISTORY, position));
- return populateHistoryInfoFromCursor(info, historyCursor);
- }
- return null;
- }
-
- protected static HomeContextMenuInfo populateHistoryInfoFromCursor(HomeContextMenuInfo info, Cursor cursor) {
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- info.itemType = HomeContextMenuInfo.RemoveItemType.HISTORY;
- final int bookmarkIdCol = cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID);
- if (cursor.isNull(bookmarkIdCol)) {
- // If this is a combined cursor, we may get a history item without a
- // bookmark, in which case the bookmarks ID column value will be null.
- info.bookmarkId = -1;
- } else {
- info.bookmarkId = cursor.getInt(bookmarkIdCol);
- }
- return info;
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
deleted file mode 100644
index a2c1b72c2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-import org.mozilla.gecko.home.RecentTabsAdapter.ClosedTab;
-
-public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
- private static final String LOGTAG = "CombinedHistoryItem";
-
- public CombinedHistoryItem(View view) {
- super(view);
- }
-
- public enum ItemType {
- CLIENT, HIDDEN_DEVICES, SECTION_HEADER, HISTORY, NAVIGATION_BACK, CHILD, SYNCED_DEVICES,
- RECENT_TABS, CLOSED_TAB;
-
- public static ItemType viewTypeToItemType(int viewType) {
- if (viewType >= ItemType.values().length) {
- Log.e(LOGTAG, "No corresponding ItemType!");
- }
- return ItemType.values()[viewType];
- }
-
- public static int itemTypeToViewType(ItemType itemType) {
- return itemType.ordinal();
- }
- }
-
- public static class BasicItem extends CombinedHistoryItem {
- public BasicItem(View view) {
- super(view);
- }
- }
-
- public static class SmartFolder extends CombinedHistoryItem {
- final Context context;
- final ImageView icon;
- final TextView title;
- final TextView subtext;
-
- public SmartFolder(View view) {
- super(view);
- context = view.getContext();
-
- icon = (ImageView) view.findViewById(R.id.device_type);
- title = (TextView) view.findViewById(R.id.title);
- subtext = (TextView) view.findViewById(R.id.subtext);
- }
-
- public void bind(int drawableRes, int titleRes, int singleDeviceRes, int multiDeviceRes, int numDevices) {
- icon.setImageResource(drawableRes);
- title.setText(titleRes);
- final String subtitle = numDevices == 1 ? context.getString(singleDeviceRes) : context.getString(multiDeviceRes, numDevices);
- subtext.setText(subtitle);
- }
- }
-
- public static class HistoryItem extends CombinedHistoryItem {
- public HistoryItem(View view) {
- super(view);
- }
-
- public void bind(Cursor historyCursor) {
- final TwoLinePageRow pageRow = (TwoLinePageRow) this.itemView;
- pageRow.setShowIcons(true);
- pageRow.updateFromCursor(historyCursor);
- }
-
- public void bind(RemoteTab remoteTab) {
- final TwoLinePageRow childPageRow = (TwoLinePageRow) this.itemView;
- childPageRow.setShowIcons(true);
- childPageRow.update(remoteTab.title, remoteTab.url);
- }
-
- public void bind(ClosedTab closedTab) {
- final TwoLinePageRow childPageRow = (TwoLinePageRow) this.itemView;
- childPageRow.setShowIcons(false);
- childPageRow.update(closedTab.title, closedTab.url);
- }
- }
-
- public static class ClientItem extends CombinedHistoryItem {
- final TextView nameView;
- final ImageView deviceTypeView;
- final TextView lastModifiedView;
- final ImageView deviceExpanded;
-
- public ClientItem(View view) {
- super(view);
- nameView = (TextView) view.findViewById(R.id.client);
- deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
- lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
- deviceExpanded = (ImageView) view.findViewById(R.id.device_expanded);
- }
-
- public void bind(Context context, RemoteClient client, boolean isCollapsed) {
- this.nameView.setText(client.name);
- final long now = System.currentTimeMillis();
- this.lastModifiedView.setText(ClientsAdapter.getLastSyncedString(context, now, client.lastModified));
-
- if (client.isDesktop()) {
- deviceTypeView.setImageResource(isCollapsed ? R.drawable.sync_desktop_inactive : R.drawable.sync_desktop);
- } else {
- deviceTypeView.setImageResource(isCollapsed ? R.drawable.sync_mobile_inactive : R.drawable.sync_mobile);
- }
-
- nameView.setTextColor(ContextCompat.getColor(context, isCollapsed ? R.color.tabs_tray_icon_grey : R.color.placeholder_active_grey));
- if (client.tabs.size() > 0) {
- deviceExpanded.setImageResource(isCollapsed ? R.drawable.home_group_collapsed : R.drawable.arrow_down);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
deleted file mode 100644
index c9afecd63..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ /dev/null
@@ -1,697 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.accounts.Account;
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.annotation.UiThread;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.UnderlineSpan;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.RemoteClientsDialogFragment;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.SyncStatusListener;
-import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.widget.HistoryDividerItemDecoration;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.PARENT;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_SYNC;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-
-public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsDialogFragment.RemoteClientsListener {
- private static final String LOGTAG = "GeckoCombinedHistoryPnl";
-
- private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
- private final int LOADER_ID_HISTORY = 0;
- private final int LOADER_ID_REMOTE = 1;
-
- // String placeholders to mark formatting.
- private final static String FORMAT_S1 = "%1$s";
- private final static String FORMAT_S2 = "%2$s";
-
- private CombinedHistoryRecyclerView mRecyclerView;
- private CombinedHistoryAdapter mHistoryAdapter;
- private ClientsAdapter mClientsAdapter;
- private RecentTabsAdapter mRecentTabsAdapter;
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- private Bundle mSavedRestoreBundle;
-
- private PanelLevel mPanelLevel;
- private Button mPanelFooterButton;
-
- private PanelStateUpdateHandler mPanelStateUpdateHandler;
-
- // Child refresh layout view.
- protected SwipeRefreshLayout mRefreshLayout;
-
- // Sync listener that stops refreshing when a sync is completed.
- protected RemoteTabsSyncListener mSyncStatusListener;
-
- // Reference to the View to display when there are no results.
- private View mHistoryEmptyView;
- private View mClientsEmptyView;
- private View mRecentTabsEmptyView;
-
- public interface OnPanelLevelChangeListener {
- enum PanelLevel {
- PARENT, CHILD_SYNC, CHILD_RECENT_TABS
- }
-
- /**
- * Propagates level changes.
- * @param level
- * @return true if level changed, false otherwise.
- */
- boolean changeLevel(PanelLevel level);
- }
-
- @Override
- public void onCreate(Bundle savedInstance) {
- super.onCreate(savedInstance);
-
- int cachedRecentTabsCount = 0;
- if (mPanelStateChangeListener != null ) {
- cachedRecentTabsCount = mPanelStateChangeListener.getCachedRecentTabsCount();
- }
- mHistoryAdapter = new CombinedHistoryAdapter(getResources(), cachedRecentTabsCount);
- if (mPanelStateChangeListener != null) {
- mHistoryAdapter.setPanelStateChangeListener(mPanelStateChangeListener);
- }
-
- mClientsAdapter = new ClientsAdapter(getContext());
- // The RecentTabsAdapter doesn't use a cursor and therefore can't use the CursorLoader's
- // onLoadFinished() callback for updating the panel state when the closed tab count changes.
- // Instead, we provide it with independent callbacks as necessary.
- mRecentTabsAdapter = new RecentTabsAdapter(getContext(),
- mHistoryAdapter.getRecentTabsUpdateHandler(), getPanelStateUpdateHandler());
-
- mSyncStatusListener = new RemoteTabsSyncListener();
- FirefoxAccounts.addSyncStatusListener(mSyncStatusListener);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.home_combined_history_panel, container, false);
- }
-
- @UiThread
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- mRecyclerView = (CombinedHistoryRecyclerView) view.findViewById(R.id.combined_recycler_view);
- setUpRecyclerView();
-
- mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
- setUpRefreshLayout();
-
- mClientsEmptyView = view.findViewById(R.id.home_clients_empty_view);
- mHistoryEmptyView = view.findViewById(R.id.home_history_empty_view);
- mRecentTabsEmptyView = view.findViewById(R.id.home_recent_tabs_empty_view);
- setUpEmptyViews();
-
- mPanelFooterButton = (Button) view.findViewById(R.id.history_panel_footer_button);
- mPanelFooterButton.setText(R.string.home_clear_history_button);
- mPanelFooterButton.setOnClickListener(new OnFooterButtonClickListener());
-
- mRecentTabsAdapter.startListeningForClosedTabs();
- mRecentTabsAdapter.startListeningForHistorySanitize();
-
- if (mSavedRestoreBundle != null) {
- setPanelStateFromBundle(mSavedRestoreBundle);
- mSavedRestoreBundle = null;
- }
- }
-
- @UiThread
- private void setUpRecyclerView() {
- if (mPanelLevel == null) {
- mPanelLevel = PARENT;
- }
-
- mRecyclerView.setAdapter(mPanelLevel == PARENT ? mHistoryAdapter :
- mPanelLevel == CHILD_SYNC ? mClientsAdapter : mRecentTabsAdapter);
-
- final RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
- animator.setAddDuration(100);
- animator.setChangeDuration(100);
- animator.setMoveDuration(100);
- animator.setRemoveDuration(100);
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mHistoryAdapter.setLinearLayoutManager((LinearLayoutManager) mRecyclerView.getLayoutManager());
- mRecyclerView.setItemAnimator(animator);
- mRecyclerView.addItemDecoration(new HistoryDividerItemDecoration(getContext()));
- mRecyclerView.setOnHistoryClickedListener(mUrlOpenListener);
- mRecyclerView.setOnPanelLevelChangeListener(new OnLevelChangeListener());
- mRecyclerView.setHiddenClientsDialogBuilder(new HiddenClientsHelper());
- mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView, dx, dy);
- final LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
- if ((mPanelLevel == PARENT) && (llm.findLastCompletelyVisibleItemPosition() == HistoryCursorLoader.HISTORY_LIMIT)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.LIST, "history_scroll_max");
- }
-
- }
- });
- registerForContextMenu(mRecyclerView);
- }
-
- private void setUpRefreshLayout() {
- mRefreshLayout.setColorSchemeResources(R.color.fennec_ui_orange, R.color.action_orange);
- mRefreshLayout.setOnRefreshListener(new RemoteTabsRefreshListener());
- mRefreshLayout.setEnabled(false);
- }
-
- private void setUpEmptyViews() {
- // Set up history empty view.
- final ImageView historyIcon = (ImageView) mHistoryEmptyView.findViewById(R.id.home_empty_image);
- historyIcon.setVisibility(View.GONE);
-
- final TextView historyText = (TextView) mHistoryEmptyView.findViewById(R.id.home_empty_text);
- historyText.setText(R.string.home_most_recent_empty);
-
- final TextView historyHint = (TextView) mHistoryEmptyView.findViewById(R.id.home_empty_hint);
-
- if (!Restrictions.isAllowed(getActivity(), Restrictable.PRIVATE_BROWSING)) {
- historyHint.setVisibility(View.GONE);
- } else {
- final String hintText = getResources().getString(R.string.home_most_recent_emptyhint);
- final SpannableStringBuilder hintBuilder = formatHintText(hintText);
- if (hintBuilder != null) {
- historyHint.setText(hintBuilder);
- historyHint.setMovementMethod(LinkMovementMethod.getInstance());
- historyHint.setVisibility(View.VISIBLE);
- }
- }
-
- // Set up Clients empty view.
- final Button syncSetupButton = (Button) mClientsEmptyView.findViewById(R.id.sync_setup_button);
- syncSetupButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "history_syncsetup");
- // This Activity will redirect to the correct Activity as needed.
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
- startActivity(intent);
- }
- });
-
- // Set up Recent Tabs empty view.
- final ImageView recentTabsIcon = (ImageView) mRecentTabsEmptyView.findViewById(R.id.home_empty_image);
- recentTabsIcon.setImageResource(R.drawable.icon_remote_tabs_empty);
-
- final TextView recentTabsText = (TextView) mRecentTabsEmptyView.findViewById(R.id.home_empty_text);
- recentTabsText.setText(R.string.home_last_tabs_empty);
- }
-
- @Override
- public void setPanelStateChangeListener(
- PanelStateChangeListener panelStateChangeListener) {
- super.setPanelStateChangeListener(panelStateChangeListener);
- if (mHistoryAdapter != null) {
- mHistoryAdapter.setPanelStateChangeListener(panelStateChangeListener);
- }
- }
-
- @Override
- public void restoreData(Bundle data) {
- if (mRecyclerView != null) {
- setPanelStateFromBundle(data);
- } else {
- mSavedRestoreBundle = data;
- }
- }
-
- private void setPanelStateFromBundle(Bundle data) {
- if (data != null && data.getBoolean("goToRecentTabs", false) && mPanelLevel != CHILD_RECENT_TABS) {
- mPanelLevel = CHILD_RECENT_TABS;
- mRecyclerView.swapAdapter(mRecentTabsAdapter, true);
- updateEmptyView(CHILD_RECENT_TABS);
- updateButtonFromLevel();
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- }
-
- @Override
- protected void load() {
- getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
- getLoaderManager().initLoader(LOADER_ID_REMOTE, null, mCursorLoaderCallbacks);
- }
-
- private static class RemoteTabsCursorLoader extends SimpleCursorLoader {
- private final GeckoProfile mProfile;
-
- public RemoteTabsCursorLoader(Context context) {
- super(context);
- mProfile = GeckoProfile.get(context);
- }
-
- @Override
- public Cursor loadCursor() {
- return BrowserDB.from(mProfile).getTabsAccessor().getRemoteTabsCursor(getContext());
- }
- }
-
- private static class HistoryCursorLoader extends SimpleCursorLoader {
- // Max number of history results
- public static final int HISTORY_LIMIT = 100;
- private final BrowserDB mDB;
-
- public HistoryCursorLoader(Context context) {
- super(context);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final ContentResolver cr = getContext().getContentResolver();
- return mDB.getRecentHistory(cr, HISTORY_LIMIT);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- private BrowserDB mDB; // Pseudo-final: set in onCreateLoader.
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (mDB == null) {
- mDB = BrowserDB.from(getActivity());
- }
-
- switch (id) {
- case LOADER_ID_HISTORY:
- return new HistoryCursorLoader(getContext());
- case LOADER_ID_REMOTE:
- return new RemoteTabsCursorLoader(getContext());
- default:
- Log.e(LOGTAG, "Unknown loader id!");
- return null;
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- final int loaderId = loader.getId();
- switch (loaderId) {
- case LOADER_ID_HISTORY:
- mHistoryAdapter.setHistory(c);
- updateEmptyView(PARENT);
- break;
-
- case LOADER_ID_REMOTE:
- final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
- mHistoryAdapter.getDeviceUpdateHandler().onDeviceCountUpdated(clients.size());
- mClientsAdapter.setClients(clients);
- updateEmptyView(CHILD_SYNC);
- break;
- }
-
- updateButtonFromLevel();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- mClientsAdapter.setClients(Collections.<RemoteClient>emptyList());
- mHistoryAdapter.setHistory(null);
- }
- }
-
- public interface PanelStateUpdateHandler {
- void onPanelStateUpdated(PanelLevel level);
- }
-
- public PanelStateUpdateHandler getPanelStateUpdateHandler() {
- if (mPanelStateUpdateHandler == null) {
- mPanelStateUpdateHandler = new PanelStateUpdateHandler() {
- @Override
- public void onPanelStateUpdated(PanelLevel level) {
- updateEmptyView(level);
- updateButtonFromLevel();
- }
- };
- }
- return mPanelStateUpdateHandler;
- }
-
- protected class OnLevelChangeListener implements OnPanelLevelChangeListener {
- @Override
- public boolean changeLevel(PanelLevel level) {
- if (level == mPanelLevel) {
- return false;
- }
-
- mPanelLevel = level;
- switch (level) {
- case PARENT:
- mRecyclerView.swapAdapter(mHistoryAdapter, true);
- mRefreshLayout.setEnabled(false);
- break;
- case CHILD_SYNC:
- mRecyclerView.swapAdapter(mClientsAdapter, true);
- mRefreshLayout.setEnabled(mClientsAdapter.getClientsCount() > 0);
- break;
- case CHILD_RECENT_TABS:
- mRecyclerView.swapAdapter(mRecentTabsAdapter, true);
- break;
- }
-
- updateEmptyView(level);
- updateButtonFromLevel();
- return true;
- }
- }
-
- private void updateButtonFromLevel() {
- switch (mPanelLevel) {
- case PARENT:
- final boolean historyRestricted = !Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY);
- if (historyRestricted || mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders()) {
- mPanelFooterButton.setVisibility(View.GONE);
- } else {
- mPanelFooterButton.setText(R.string.home_clear_history_button);
- mPanelFooterButton.setVisibility(View.VISIBLE);
- }
- break;
- case CHILD_RECENT_TABS:
- if (mRecentTabsAdapter.getClosedTabsCount() > 1) {
- mPanelFooterButton.setText(R.string.home_restore_all);
- mPanelFooterButton.setVisibility(View.VISIBLE);
- } else {
- mPanelFooterButton.setVisibility(View.GONE);
- }
- break;
- case CHILD_SYNC:
- mPanelFooterButton.setVisibility(View.GONE);
- break;
- }
- }
-
- private class OnFooterButtonClickListener implements View.OnClickListener {
- @Override
- public void onClick(View view) {
- switch (mPanelLevel) {
- case PARENT:
- final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
- dialogBuilder.setMessage(R.string.home_clear_history_confirm);
- dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- }
- });
-
- dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
-
- // Send message to Java to clear history.
- final JSONObject json = new JSONObject();
- try {
- json.put("history", true);
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
-
- GeckoAppShell.notifyObservers("Sanitize:ClearData", json.toString());
- Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
- }
- });
-
- dialogBuilder.show();
- break;
- case CHILD_RECENT_TABS:
- final String telemetryExtra = mRecentTabsAdapter.restoreAllTabs();
- if (telemetryExtra != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON, telemetryExtra);
- }
- break;
- }
- }
- }
-
- private void updateEmptyView(PanelLevel level) {
- boolean showEmptyHistoryView = (mPanelLevel == PARENT && mHistoryEmptyView.isShown());
- boolean showEmptyClientsView = (mPanelLevel == CHILD_SYNC && mClientsEmptyView.isShown());
- boolean showEmptyRecentTabsView = (mPanelLevel == CHILD_RECENT_TABS && mRecentTabsEmptyView.isShown());
-
- if (mPanelLevel == level) {
- switch (mPanelLevel) {
- case PARENT:
- showEmptyHistoryView = mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders();
- break;
-
- case CHILD_SYNC:
- showEmptyClientsView = mClientsAdapter.getItemCount() == 1;
- break;
-
- case CHILD_RECENT_TABS:
- showEmptyRecentTabsView = mRecentTabsAdapter.getClosedTabsCount() == 0;
- break;
- }
- }
-
- final boolean showEmptyView = showEmptyClientsView || showEmptyHistoryView || showEmptyRecentTabsView;
- mRecyclerView.setOverScrollMode(showEmptyView ? View.OVER_SCROLL_NEVER : View.OVER_SCROLL_IF_CONTENT_SCROLLS);
-
- mHistoryEmptyView.setVisibility(showEmptyHistoryView ? View.VISIBLE : View.GONE);
- mClientsEmptyView.setVisibility(showEmptyClientsView ? View.VISIBLE : View.GONE);
- mRecentTabsEmptyView.setVisibility(showEmptyRecentTabsView ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Make Span that is clickable, and underlined
- * between the string markers <code>FORMAT_S1</code> and
- * <code>FORMAT_S2</code>.
- *
- * @param text String to format
- * @return formatted SpannableStringBuilder, or null if there
- * is not any text to format.
- */
- private SpannableStringBuilder formatHintText(String text) {
- // Set formatting as marked by string placeholders.
- final int underlineStart = text.indexOf(FORMAT_S1);
- final int underlineEnd = text.indexOf(FORMAT_S2);
-
- // Check that there is text to be formatted.
- if (underlineStart >= underlineEnd) {
- return null;
- }
-
- final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-
- // Set clickable text.
- final ClickableSpan clickableSpan = new ClickableSpan() {
- @Override
- public void onClick(View widget) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "hint_private_browsing");
- try {
- final JSONObject json = new JSONObject();
- json.put("type", "Menu:Open");
- GeckoApp.getEventDispatcher().dispatchEvent(json, null);
- EventDispatcher.getInstance().dispatchEvent(json, null);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error forming JSON for Private Browsing contextual hint", e);
- }
- }
- };
-
- ssb.setSpan(clickableSpan, 0, text.length(), 0);
-
- // Remove underlining set by ClickableSpan.
- final UnderlineSpan noUnderlineSpan = new UnderlineSpan() {
- @Override
- public void updateDrawState(TextPaint textPaint) {
- textPaint.setUnderlineText(false);
- }
- };
-
- ssb.setSpan(noUnderlineSpan, 0, text.length(), 0);
-
- // Add underlining for "Private Browsing".
- ssb.setSpan(new UnderlineSpan(), underlineStart, underlineEnd, 0);
-
- ssb.delete(underlineEnd, underlineEnd + FORMAT_S2.length());
- ssb.delete(underlineStart, underlineStart + FORMAT_S1.length());
-
- return ssb;
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
- // Long pressed item was not a RemoteTabsGroup item. Superclass
- // can handle this.
- super.onCreateContextMenu(menu, view, menuInfo);
- return;
- }
-
- // Long pressed item was a remote client; provide the appropriate menu.
- final MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.home_remote_tabs_client_contextmenu, menu);
-
- final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
- menu.setHeaderTitle(info.client.name);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (super.onContextItemSelected(item)) {
- // HomeFragment was able to handle to selected item.
- return true;
- }
-
- final ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
- return false;
- }
-
- final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
-
- final int itemId = item.getItemId();
- if (itemId == R.id.home_remote_tabs_hide_client) {
- mClientsAdapter.removeItem(info.position);
- return true;
- }
-
- return false;
- }
-
- interface DialogBuilder<E> {
- void createAndShowDialog(List<E> items);
- }
-
- protected class HiddenClientsHelper implements DialogBuilder<RemoteClient> {
- @Override
- public void createAndShowDialog(List<RemoteClient> clientsList) {
- final RemoteClientsDialogFragment dialog = RemoteClientsDialogFragment.newInstance(
- getResources().getString(R.string.home_remote_tabs_hidden_devices_title),
- getResources().getString(R.string.home_remote_tabs_unhide_selected_devices),
- RemoteClientsDialogFragment.ChoiceMode.MULTIPLE, new ArrayList<>(clientsList));
- dialog.setTargetFragment(CombinedHistoryPanel.this, 0);
- dialog.show(getActivity().getSupportFragmentManager(), "show-clients");
- }
- }
-
- @Override
- public void onClients(List<RemoteClient> clients) {
- mClientsAdapter.unhideClients(clients);
- }
-
- /**
- * Stores information regarding the creation of the context menu for a remote client.
- */
- protected static class RemoteTabsClientContextMenuInfo extends HomeContextMenuInfo {
- protected final RemoteClient client;
-
- public RemoteTabsClientContextMenuInfo(View targetView, int position, long id, RemoteClient client) {
- super(targetView, position, id);
- this.client = client;
- }
- }
-
- protected class RemoteTabsRefreshListener implements SwipeRefreshLayout.OnRefreshListener {
- @Override
- public void onRefresh() {
- if (FirefoxAccounts.firefoxAccountsExist(getActivity())) {
- final Account account = FirefoxAccounts.getFirefoxAccount(getActivity());
- FirefoxAccounts.requestImmediateSync(account, STAGES_TO_SYNC_ON_REFRESH, null);
- } else {
- Log.wtf(LOGTAG, "No Firefox Account found; this should never happen. Ignoring.");
- mRefreshLayout.setRefreshing(false);
- }
- }
- }
-
- protected class RemoteTabsSyncListener implements SyncStatusListener {
- @Override
- public Context getContext() {
- return getActivity();
- }
-
- @Override
- public Account getAccount() {
- return FirefoxAccounts.getFirefoxAccount(getContext());
- }
-
- @Override
- public void onSyncStarted() {
- }
-
- @Override
- public void onSyncFinished() {
- mRefreshLayout.setRefreshing(false);
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- mRecentTabsAdapter.stopListeningForClosedTabs();
- mRecentTabsAdapter.stopListeningForHistorySanitize();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mSyncStatusListener != null) {
- FirefoxAccounts.removeSyncStatusListener(mSyncStatusListener);
- mSyncStatusListener = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
deleted file mode 100644
index e813e4c44..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_SYNC;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.PARENT;
-
-public class CombinedHistoryRecyclerView extends RecyclerView
- implements RecyclerViewClickSupport.OnItemClickListener, RecyclerViewClickSupport.OnItemLongClickListener {
- public static String LOGTAG = "CombinedHistoryRecycView";
-
- protected interface AdapterContextMenuBuilder {
- HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position);
- }
-
- protected HomePager.OnUrlOpenListener mOnUrlOpenListener;
- protected OnPanelLevelChangeListener mOnPanelLevelChangeListener;
- protected CombinedHistoryPanel.DialogBuilder<RemoteClient> mDialogBuilder;
- protected HomeContextMenuInfo mContextMenuInfo;
-
- public CombinedHistoryRecyclerView(Context context) {
- super(context);
- init(context);
- }
-
- public CombinedHistoryRecyclerView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- init(context);
- }
-
- public CombinedHistoryRecyclerView(Context context, AttributeSet attributeSet, int defStyle) {
- super(context, attributeSet, defStyle);
- init(context);
- }
-
- private void init(Context context) {
- LinearLayoutManager layoutManager = new LinearLayoutManager(context);
- layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- setLayoutManager(layoutManager);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this)
- .setOnItemLongClickListener(this);
-
- setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- final int action = event.getAction();
-
- // If the user hit the BACK key, try to move to the parent folder.
- if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return mOnPanelLevelChangeListener.changeLevel(PARENT);
- }
- return false;
- }
- });
- }
-
- public void setOnHistoryClickedListener(HomePager.OnUrlOpenListener listener) {
- this.mOnUrlOpenListener = listener;
- }
-
- public void setOnPanelLevelChangeListener(OnPanelLevelChangeListener listener) {
- this.mOnPanelLevelChangeListener = listener;
- }
-
- public void setHiddenClientsDialogBuilder(CombinedHistoryPanel.DialogBuilder<RemoteClient> builder) {
- mDialogBuilder = builder;
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- final int viewType = getAdapter().getItemViewType(position);
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
- final String telemetryExtra;
-
- switch (itemType) {
- case RECENT_TABS:
- mOnPanelLevelChangeListener.changeLevel(CHILD_RECENT_TABS);
- break;
-
- case SYNCED_DEVICES:
- mOnPanelLevelChangeListener.changeLevel(CHILD_SYNC);
- break;
-
- case CLIENT:
- ((ClientsAdapter) getAdapter()).toggleClient(position);
- break;
-
- case HIDDEN_DEVICES:
- if (mDialogBuilder != null) {
- mDialogBuilder.createAndShowDialog(((ClientsAdapter) getAdapter()).getHiddenClients());
- }
- break;
-
- case NAVIGATION_BACK:
- mOnPanelLevelChangeListener.changeLevel(PARENT);
- break;
-
- case CHILD:
- case HISTORY:
- if (mOnUrlOpenListener != null) {
- final TwoLinePageRow historyItem = (TwoLinePageRow) v;
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "history");
- mOnUrlOpenListener.onUrlOpen(historyItem.getUrl(), EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- break;
-
- case CLOSED_TAB:
- telemetryExtra = ((RecentTabsAdapter) getAdapter()).restoreTabFromPosition(position);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, telemetryExtra);
- break;
- }
- }
-
- @Override
- public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
- mContextMenuInfo = ((AdapterContextMenuBuilder) getAdapter()).makeContextMenuInfoFromPosition(v, position);
- return showContextMenuForChild(this);
- }
-
- @Override
- public HomeContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
deleted file mode 100644
index d2c136219..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelLayout.ContextMenuRegistry;
-import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
-import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * Fragment that displays dynamic content specified by a {@code PanelConfig}.
- * The {@code DynamicPanel} UI is built based on the given {@code LayoutType}
- * and its associated list of {@code ViewConfig}.
- *
- * {@code DynamicPanel} manages all necessary Loaders to load panel datasets
- * from their respective content providers. Each panel dataset has its own
- * associated Loader. This is enforced by defining the Loader IDs based on
- * their associated dataset IDs.
- *
- * The {@code PanelLayout} can make load and reset requests on datasets via
- * the provided {@code DatasetHandler}. This way it doesn't need to know the
- * details of how datasets are loaded and reset. Each time a dataset is
- * requested, {@code DynamicPanel} restarts a Loader with the respective ID (see
- * {@code PanelDatasetHandler}).
- *
- * See {@code PanelLayout} for more details on how {@code DynamicPanel}
- * receives dataset requests and delivers them back to the {@code PanelLayout}.
- */
-public class DynamicPanel extends HomeFragment {
- private static final String LOGTAG = "GeckoDynamicPanel";
-
- // Dataset ID to be used by the loader
- private static final String DATASET_REQUEST = "dataset_request";
-
- // Max number of items to display in the panel
- private static final int RESULT_LIMIT = 100;
-
- // The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
- private FrameLayout mView;
-
- // The panel layout associated with this panel
- private PanelLayout mPanelLayout;
-
- // The layout used to show authentication UI for this panel
- private PanelAuthLayout mPanelAuthLayout;
-
- // Cache used to keep track of whether or not the user has been authenticated.
- private PanelAuthCache mPanelAuthCache;
-
- // Hold a reference to the UiAsyncTask we use to check the state of the
- // PanelAuthCache, so that we can cancel it if necessary.
- private UIAsyncTask.WithoutParams<Boolean> mAuthStateTask;
-
- // The configuration associated with this panel
- private PanelConfig mPanelConfig;
-
- // Callbacks used for the loader
- private PanelLoaderCallbacks mLoaderCallbacks;
-
- // The current UI mode in the fragment
- private UIMode mUIMode;
-
- /*
- * Different UI modes to display depending on the authentication state.
- *
- * PANEL: Layout to display panel data.
- * AUTH: Authentication UI.
- */
- private enum UIMode {
- PANEL,
- AUTH
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle args = getArguments();
- if (args != null) {
- mPanelConfig = (PanelConfig) args.getParcelable(HomePager.PANEL_CONFIG_ARG);
- }
-
- if (mPanelConfig == null) {
- throw new IllegalStateException("Can't create a DynamicPanel without a PanelConfig");
- }
-
- mPanelAuthCache = new PanelAuthCache(getActivity());
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- mView = new FrameLayout(getActivity());
- return mView;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- // Restore whatever the UI mode the fragment had before
- // a device rotation.
- if (mUIMode != null) {
- setUIMode(mUIMode);
- }
-
- mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mView = null;
- mPanelLayout = null;
- mPanelAuthLayout = null;
-
- mPanelAuthCache.setOnChangeListener(null);
-
- if (mAuthStateTask != null) {
- mAuthStateTask.cancel();
- mAuthStateTask = null;
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // Create callbacks before the initial loader is started.
- mLoaderCallbacks = new PanelLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- protected void load() {
- Log.d(LOGTAG, "Loading layout");
-
- if (requiresAuth()) {
- mAuthStateTask = new UIAsyncTask.WithoutParams<Boolean>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public synchronized Boolean doInBackground() {
- return mPanelAuthCache.isAuthenticated(mPanelConfig.getId());
- }
-
- @Override
- public void onPostExecute(Boolean isAuthenticated) {
- mAuthStateTask = null;
- setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
- }
- };
- mAuthStateTask.execute();
- } else {
- setUIMode(UIMode.PANEL);
- }
- }
-
- /**
- * @return true if this panel requires authentication.
- */
- private boolean requiresAuth() {
- return mPanelConfig.getAuthConfig() != null;
- }
-
- /**
- * Lazily creates layout for panel data.
- */
- private void createPanelLayout() {
- final ContextMenuRegistry contextMenuRegistry = new ContextMenuRegistry() {
- @Override
- public void register(View view) {
- registerForContextMenu(view);
- }
- };
-
- switch (mPanelConfig.getLayoutType()) {
- case FRAME:
- final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
- mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler,
- mUrlOpenListener, contextMenuRegistry);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
- }
-
- Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
- mView.addView(mPanelLayout);
- }
-
- /**
- * Lazily creates layout for authentication UI.
- */
- private void createPanelAuthLayout() {
- mPanelAuthLayout = new PanelAuthLayout(getActivity(), mPanelConfig);
- mView.addView(mPanelAuthLayout, 0);
- }
-
- private void setUIMode(UIMode mode) {
- switch (mode) {
- case PANEL:
- if (mPanelAuthLayout != null) {
- mPanelAuthLayout.setVisibility(View.GONE);
- }
- if (mPanelLayout == null) {
- createPanelLayout();
- }
- mPanelLayout.setVisibility(View.VISIBLE);
-
- // Only trigger a reload if the UI mode has changed
- // (e.g. auth cache changes) and the fragment is allowed
- // to load its contents. Any loaders associated with the
- // panel layout will be automatically re-bound after a
- // device rotation, no need to explicitly load it again.
- if (mUIMode != mode && canLoad()) {
- mPanelLayout.load();
- }
- break;
-
- case AUTH:
- if (mPanelLayout != null) {
- mPanelLayout.setVisibility(View.GONE);
- }
- if (mPanelAuthLayout == null) {
- createPanelAuthLayout();
- }
- mPanelAuthLayout.setVisibility(View.VISIBLE);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
- }
-
- mUIMode = mode;
- }
-
- /**
- * Used by the PanelLayout to make load and reset requests to
- * the holding fragment.
- */
- private class PanelDatasetHandler implements DatasetHandler {
- @Override
- public void requestDataset(DatasetRequest request) {
- Log.d(LOGTAG, "Requesting request: " + request);
-
- final Bundle bundle = new Bundle();
- bundle.putParcelable(DATASET_REQUEST, request);
-
- getLoaderManager().restartLoader(request.getViewIndex(),
- bundle, mLoaderCallbacks);
- }
-
- @Override
- public void resetDataset(int viewIndex) {
- Log.d(LOGTAG, "Resetting dataset: " + viewIndex);
-
- final LoaderManager lm = getLoaderManager();
-
- // Release any resources associated with the dataset if
- // it's currently loaded in memory.
- final Loader<?> datasetLoader = lm.getLoader(viewIndex);
- if (datasetLoader != null) {
- datasetLoader.reset();
- }
- }
- }
-
- /**
- * Cursor loader for the panel datasets.
- */
- private static class PanelDatasetLoader extends SimpleCursorLoader {
- private DatasetRequest mRequest;
-
- public PanelDatasetLoader(Context context, DatasetRequest request) {
- super(context);
- mRequest = request;
- }
-
- public DatasetRequest getRequest() {
- return mRequest;
- }
-
- @Override
- public void onContentChanged() {
- // Ensure the refresh request doesn't affect the view's filter
- // stack (i.e. use DATASET_LOAD type) but keep the current
- // dataset ID and filter.
- final DatasetRequest newRequest =
- new DatasetRequest(mRequest.getViewIndex(),
- DatasetRequest.Type.DATASET_LOAD,
- mRequest.getDatasetId(),
- mRequest.getFilterDetail());
-
- mRequest = newRequest;
- super.onContentChanged();
- }
-
- @Override
- public Cursor loadCursor() {
- final ContentResolver cr = getContext().getContentResolver();
-
- final String selection;
- final String[] selectionArgs;
-
- // Null represents the root filter
- if (mRequest.getFilter() == null) {
- selection = HomeItems.FILTER + " IS NULL";
- selectionArgs = null;
- } else {
- selection = HomeItems.FILTER + " = ?";
- selectionArgs = new String[] { mRequest.getFilter() };
- }
-
- final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
- mRequest.getDatasetId())
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(RESULT_LIMIT))
- .build();
-
- // XXX: You can use HomeItems.CONTENT_FAKE_URI for development
- // to pull items from fake_home_items.json.
- return cr.query(queryUri, null, selection, selectionArgs, null);
- }
- }
-
- /**
- * LoaderCallbacks implementation that interacts with the LoaderManager.
- */
- private class PanelLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final DatasetRequest request = (DatasetRequest) args.getParcelable(DATASET_REQUEST);
-
- Log.d(LOGTAG, "Creating loader for request: " + request);
- return new PanelDatasetLoader(getActivity(), request);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- final DatasetRequest request = getRequestFromLoader(loader);
- Log.d(LOGTAG, "Finished loader for request: " + request);
-
- if (mPanelLayout != null) {
- mPanelLayout.deliverDataset(request, cursor);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- final DatasetRequest request = getRequestFromLoader(loader);
- Log.d(LOGTAG, "Resetting loader for request: " + request);
-
- if (mPanelLayout != null) {
- mPanelLayout.releaseDataset(request.getViewIndex());
- }
- }
-
- private DatasetRequest getRequestFromLoader(Loader<Cursor> loader) {
- final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
- return datasetLoader.getRequest();
- }
- }
-
- private class PanelAuthChangeListener implements PanelAuthCache.OnChangeListener {
- @Override
- public void onChange(String panelId, boolean isAuthenticated) {
- if (!mPanelConfig.getId().equals(panelId)) {
- return;
- }
-
- setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java
deleted file mode 100644
index 7168c1576..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.View;
-
-class FramePanelLayout extends PanelLayout {
- private static final String LOGTAG = "GeckoFramePanelLayout";
-
- private final View mChildView;
- private final ViewConfig mChildConfig;
-
- public FramePanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
- OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
- super(context, panelConfig, datasetHandler, urlOpenListener, contextMenuRegistry);
-
- // This layout can only hold one view so we simply
- // take the first defined view from PanelConfig.
- mChildConfig = panelConfig.getViewAt(0);
- if (mChildConfig == null) {
- throw new IllegalStateException("FramePanelLayout requires a view in PanelConfig");
- }
-
- mChildView = createPanelView(mChildConfig);
- addView(mChildView);
- }
-
- @Override
- public void load() {
- Log.d(LOGTAG, "Loading");
-
- if (mChildView instanceof DatasetBacked) {
- final FilterDetail filter = new FilterDetail(mChildConfig.getFilter(), null);
-
- final DatasetRequest request = new DatasetRequest(mChildConfig.getIndex(),
- mChildConfig.getDatasetId(),
- filter);
-
- Log.d(LOGTAG, "Requesting child request: " + request);
- requestDataset(request);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java b/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java
deleted file mode 100644
index 7a49559f6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.res.Resources;
-
-import org.mozilla.gecko.home.CombinedHistoryAdapter.SectionHeader;
-import org.mozilla.gecko.R;
-
-import java.util.Calendar;
-import java.util.Locale;
-
-
-public class HistorySectionsHelper {
-
- // Constants for different time sections.
- private static final long MS_PER_DAY = 86400000;
- private static final long MS_PER_WEEK = MS_PER_DAY * 7;
-
- public static class SectionDateRange {
- public final long start;
- public final long end;
- public final String displayName;
-
- private SectionDateRange(long start, long end, String displayName) {
- this.start = start;
- this.end = end;
- this.displayName = displayName;
- }
- }
-
- /**
- * Updates the time range in milliseconds covered by each section header and sets the title.
- * @param res Resources for fetching string names
- * @param sectionsArray Array of section bookkeeping objects
- */
- public static void updateRecentSectionOffset(final Resources res, SectionDateRange[] sectionsArray) {
- final long now = System.currentTimeMillis();
- final Calendar cal = Calendar.getInstance();
-
- // Update calendar to this day.
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 1);
- final long currentDayMS = cal.getTimeInMillis();
-
- // Calculate the start and end time for each section header and set its display text.
- sectionsArray[SectionHeader.TODAY.ordinal()] =
- new SectionDateRange(currentDayMS, now, res.getString(R.string.history_today_section));
-
- sectionsArray[SectionHeader.YESTERDAY.ordinal()] =
- new SectionDateRange(currentDayMS - MS_PER_DAY, currentDayMS, res.getString(R.string.history_yesterday_section));
-
- sectionsArray[SectionHeader.WEEK.ordinal()] =
- new SectionDateRange(currentDayMS - MS_PER_WEEK, now, res.getString(R.string.history_week_section));
-
- // Update the calendar to beginning of next month to avoid problems calculating the last day of this month.
- cal.add(Calendar.MONTH, 1);
- cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DAY_OF_MONTH));
-
- // Iterate over the remaining history sections, moving backwards in time.
- for (int i = SectionHeader.THIS_MONTH.ordinal(); i < SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
- final long end = cal.getTimeInMillis();
-
- cal.add(Calendar.MONTH, -1);
- final long start = cal.getTimeInMillis();
-
- final String displayName = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
-
- sectionsArray[i] = new SectionDateRange(start, end, displayName);
- }
-
- sectionsArray[SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal()] =
- new SectionDateRange(0L, cal.getTimeInMillis(), res.getString(R.string.history_older_section));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java
deleted file mode 100644
index 98d1ae6d8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.activitystream.ActivityStreamHomeFragment;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentStatePagerAdapter;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class HomeAdapter extends FragmentStatePagerAdapter {
-
- private final Context mContext;
- private final ArrayList<PanelInfo> mPanelInfos;
- private final Map<String, HomeFragment> mPanels;
- private final Map<String, Bundle> mRestoreBundles;
-
- private boolean mCanLoadHint;
-
- private OnAddPanelListener mAddPanelListener;
-
- private HomeFragment.PanelStateChangeListener mPanelStateChangeListener = null;
-
- public interface OnAddPanelListener {
- void onAddPanel(String title);
- }
-
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
- mPanelStateChangeListener = listener;
-
- for (Fragment fragment : mPanels.values()) {
- ((HomeFragment) fragment).setPanelStateChangeListener(listener);
- }
- }
-
- public HomeAdapter(Context context, FragmentManager fm) {
- super(fm);
-
- mContext = context;
- mCanLoadHint = HomeFragment.DEFAULT_CAN_LOAD_HINT;
-
- mPanelInfos = new ArrayList<>();
- mPanels = new HashMap<>();
- mRestoreBundles = new HashMap<>();
- }
-
- @Override
- public int getCount() {
- return mPanelInfos.size();
- }
-
- @Override
- public Fragment getItem(int position) {
- PanelInfo info = mPanelInfos.get(position);
- return Fragment.instantiate(mContext, info.getClassName(mContext), info.getArgs());
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- if (mPanelInfos.size() > 0) {
- PanelInfo info = mPanelInfos.get(position);
- return info.getTitle().toUpperCase();
- }
-
- return null;
- }
-
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- final HomeFragment fragment = (HomeFragment) super.instantiateItem(container, position);
- fragment.setPanelStateChangeListener(mPanelStateChangeListener);
-
- final String id = mPanelInfos.get(position).getId();
- mPanels.put(id, fragment);
-
- if (mRestoreBundles.containsKey(id)) {
- fragment.restoreData(mRestoreBundles.get(id));
- mRestoreBundles.remove(id);
- }
-
- return fragment;
- }
-
- public void setRestoreData(int position, Bundle data) {
- final String id = mPanelInfos.get(position).getId();
- final HomeFragment fragment = mPanels.get(id);
-
- // We have no guarantees as to whether our desired fragment is instantiated yet: therefore
- // we might need to either pass data to the fragment, or store it for later.
- if (fragment != null) {
- fragment.restoreData(data);
- } else {
- mRestoreBundles.put(id, data);
- }
-
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- final String id = mPanelInfos.get(position).getId();
-
- super.destroyItem(container, position, object);
- mPanels.remove(id);
- }
-
- public void setOnAddPanelListener(OnAddPanelListener listener) {
- mAddPanelListener = listener;
- }
-
- public int getItemPosition(String panelId) {
- for (int i = 0; i < mPanelInfos.size(); i++) {
- final String id = mPanelInfos.get(i).getId();
- if (id.equals(panelId)) {
- return i;
- }
- }
-
- return -1;
- }
-
- public String getPanelIdAtPosition(int position) {
- // getPanelIdAtPosition() might be called before HomeAdapter
- // has got its initial list of PanelConfigs. Just bail.
- if (mPanelInfos.isEmpty()) {
- return null;
- }
-
- return mPanelInfos.get(position).getId();
- }
-
- private void addPanel(PanelInfo info) {
- mPanelInfos.add(info);
-
- if (mAddPanelListener != null) {
- mAddPanelListener.onAddPanel(info.getTitle());
- }
- }
-
- public void update(List<PanelConfig> panelConfigs) {
- mPanels.clear();
- mPanelInfos.clear();
-
- if (panelConfigs != null) {
- for (PanelConfig panelConfig : panelConfigs) {
- final PanelInfo info = new PanelInfo(panelConfig);
- addPanel(info);
- }
- }
-
- notifyDataSetChanged();
- }
-
- public boolean getCanLoadHint() {
- return mCanLoadHint;
- }
-
- public void setCanLoadHint(boolean canLoadHint) {
- // We cache the last hint value so that we can use it when
- // creating new panels. See PanelInfo.getArgs().
- mCanLoadHint = canLoadHint;
-
- // Enable/disable loading on all existing panels
- for (Fragment panelFragment : mPanels.values()) {
- final HomeFragment panel = (HomeFragment) panelFragment;
- panel.setCanLoadHint(canLoadHint);
- }
- }
-
- private final class PanelInfo {
- private final PanelConfig mPanelConfig;
-
- PanelInfo(PanelConfig panelConfig) {
- mPanelConfig = panelConfig;
- }
-
- public String getId() {
- return mPanelConfig.getId();
- }
-
- public String getTitle() {
- return mPanelConfig.getTitle();
- }
-
- public String getClassName(Context context) {
- final PanelType type = mPanelConfig.getType();
-
- // Override top_sites with ActivityStream panel when enabled
- // PanelType.toString() returns the panel id
- if (type.toString() == "top_sites" &&
- ActivityStream.isEnabled(context) &&
- ActivityStream.isHomePanel()) {
- return ActivityStreamHomeFragment.class.getName();
- }
- return type.getPanelClass().getName();
- }
-
- public Bundle getArgs() {
- final Bundle args = new Bundle();
-
- args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
-
- // Only DynamicPanels need the PanelConfig argument
- if (mPanelConfig.isDynamic()) {
- args.putParcelable(HomePager.PANEL_CONFIG_ARG, mPanelConfig);
- }
-
- return args;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
deleted file mode 100644
index 10f5db39e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.EllipsisTextView;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-public class HomeBanner extends LinearLayout
- implements GeckoEventListener {
- private static final String LOGTAG = "GeckoHomeBanner";
-
- // Used for tracking scroll length
- private float mTouchY = -1;
-
- // Used to detect for upwards scroll to push banner all the way up
- private boolean mSnapBannerToTop;
-
- // Tracks whether or not the banner should be shown on the current panel.
- private boolean mActive;
-
- // The user is currently swiping between HomePager pages
- private boolean mScrollingPages;
-
- // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
- // switches back to the default page.
- private boolean mUserSwipedDown;
-
- // We must use this custom TextView to address an issue on 2.3 and lower where ellipsized text
- // will not wrap more than 2 lines.
- private final EllipsisTextView mTextView;
- private final ImageView mIconView;
-
- // The height of the banner view.
- private final float mHeight;
-
- // Listener that gets called when the banner is dismissed from the close button.
- private OnDismissListener mOnDismissListener;
-
- public interface OnDismissListener {
- public void onDismiss();
- }
-
- public HomeBanner(Context context) {
- this(context, null);
- }
-
- public HomeBanner(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.home_banner_content, this);
-
- mTextView = (EllipsisTextView) findViewById(R.id.text);
- mIconView = (ImageView) findViewById(R.id.icon);
-
- mHeight = getResources().getDimensionPixelSize(R.dimen.home_banner_height);
-
- // Disable the banner until a message is set.
- setEnabled(false);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- // Tapping on the close button will ensure that the banner is never
- // showed again on this session.
- final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
-
- // The drawable should have 50% opacity.
- closeButton.getDrawable().setAlpha(127);
-
- closeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- HomeBanner.this.dismiss();
-
- // Send the current message id back to JS.
- GeckoAppShell.notifyObservers("HomeBanner:Dismiss", (String) getTag());
- }
- });
-
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- HomeBanner.this.dismiss();
-
- // Send the current message id back to JS.
- GeckoAppShell.notifyObservers("HomeBanner:Click", (String) getTag());
- }
- });
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "HomeBanner:Data");
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "HomeBanner:Data");
- }
-
- public void setScrollingPages(boolean scrollingPages) {
- mScrollingPages = scrollingPages;
- }
-
- public void setOnDismissListener(OnDismissListener listener) {
- mOnDismissListener = listener;
- }
-
- /**
- * Hides and disables the banner.
- */
- private void dismiss() {
- setVisibility(View.GONE);
- setEnabled(false);
-
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
- }
- }
-
- /**
- * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
- */
- public void update() {
- GeckoAppShell.notifyObservers("HomeBanner:Get", null);
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- final String id = message.optString("id");
- final String text = message.optString("text");
- final String iconURI = message.optString("iconURI");
-
- // Don't update the banner if the message doesn't have valid id and text.
- if (TextUtils.isEmpty(id) || TextUtils.isEmpty(text)) {
- return;
- }
-
- // Update the banner message on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Store the current message id to pass back to JS in the view's OnClickListener.
- setTag(id);
- mTextView.setOriginalText(Html.fromHtml(text));
-
- ResourceDrawableUtils.getDrawable(getContext(), iconURI, new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(final Drawable d) {
- // Hide the image view if we don't have an icon to show.
- if (d == null) {
- mIconView.setVisibility(View.GONE);
- } else {
- mIconView.setImageDrawable(d);
- mIconView.setVisibility(View.VISIBLE);
- }
- }
- });
-
- GeckoAppShell.notifyObservers("HomeBanner:Shown", id);
-
- // Enable the banner after a message is set.
- setEnabled(true);
-
- // Animate the banner if it is currently active.
- if (mActive) {
- animateUp();
- }
- }
- });
- }
-
- public void setActive(boolean active) {
- // No need to animate if not changing
- if (mActive == active) {
- return;
- }
-
- mActive = active;
-
- // Don't animate if the banner isn't enabled.
- if (!isEnabled()) {
- return;
- }
-
- if (active) {
- animateUp();
- } else {
- animateDown();
- }
- }
-
- private void ensureVisible() {
- // The banner visibility is set to GONE after it is animated off screen,
- // so we need to make it visible again.
- if (getVisibility() == View.GONE) {
- // Translate the banner off screen before setting it to VISIBLE.
- ViewHelper.setTranslationY(this, mHeight);
- setVisibility(View.VISIBLE);
- }
- }
-
- private void animateUp() {
- // Don't try to animate if the user swiped the banner down previously to hide it.
- if (mUserSwipedDown) {
- return;
- }
-
- ensureVisible();
-
- final PropertyAnimator animator = new PropertyAnimator(100);
- animator.attach(this, Property.TRANSLATION_Y, 0);
- animator.start();
- }
-
- private void animateDown() {
- if (ViewHelper.getTranslationY(this) == mHeight) {
- // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
- setVisibility(View.GONE);
- return;
- }
-
- final PropertyAnimator animator = new PropertyAnimator(100);
- animator.attach(this, Property.TRANSLATION_Y, mHeight);
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
- setVisibility(View.GONE);
- }
- });
- animator.start();
- }
-
- public void handleHomeTouch(MotionEvent event) {
- if (!mActive || !isEnabled() || mScrollingPages) {
- return;
- }
-
- ensureVisible();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- // Track the beginning of the touch
- mTouchY = event.getRawY();
- break;
- }
-
- case MotionEvent.ACTION_MOVE: {
- final float curY = event.getRawY();
- final float delta = mTouchY - curY;
- mSnapBannerToTop = delta <= 0.0f;
-
- float newTranslationY = ViewHelper.getTranslationY(this) + delta;
-
- // Clamp the values to be between 0 and height.
- if (newTranslationY < 0.0f) {
- newTranslationY = 0.0f;
- } else if (newTranslationY > mHeight) {
- newTranslationY = mHeight;
- }
-
- // Don't change this value if it wasn't a significant movement
- if (delta >= 10 || delta <= -10) {
- mUserSwipedDown = (newTranslationY == mHeight);
- }
-
- ViewHelper.setTranslationY(this, newTranslationY);
- mTouchY = curY;
- break;
- }
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL: {
- mTouchY = -1;
- if (mSnapBannerToTop) {
- animateUp();
- } else {
- animateDown();
- mUserSwipedDown = true;
- }
- break;
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
deleted file mode 100644
index 08e79be3a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
+++ /dev/null
@@ -1,1694 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Pair;
-
-public final class HomeConfig {
- public static final String PREF_KEY_BOOKMARKS_PANEL_ENABLED = "bookmarksPanelEnabled";
- public static final String PREF_KEY_HISTORY_PANEL_ENABLED = "combinedHistoryPanelEnabled";
-
- /**
- * Used to determine what type of HomeFragment subclass to use when creating
- * a given panel. With the exception of DYNAMIC, all of these types correspond
- * to a default set of built-in panels. The DYNAMIC panel type is used by
- * third-party services to create panels with varying types of content.
- */
- @RobocopTarget
- public static enum PanelType implements Parcelable {
- TOP_SITES("top_sites", TopSitesPanel.class),
- BOOKMARKS("bookmarks", BookmarksPanel.class),
- COMBINED_HISTORY("combined_history", CombinedHistoryPanel.class),
- DYNAMIC("dynamic", DynamicPanel.class),
- // Deprecated panels that should no longer exist but are kept around for
- // migration code. Class references have been replaced with new version of the panel.
- DEPRECATED_REMOTE_TABS("remote_tabs", CombinedHistoryPanel.class),
- DEPRECATED_HISTORY("history", CombinedHistoryPanel.class),
- DEPRECATED_READING_LIST("reading_list", BookmarksPanel.class),
- DEPRECATED_RECENT_TABS("recent_tabs", CombinedHistoryPanel.class);
-
- private final String mId;
- private final Class<?> mPanelClass;
-
- PanelType(String id, Class<?> panelClass) {
- mId = id;
- mPanelClass = panelClass;
- }
-
- public static PanelType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to PanelType");
- }
-
- for (PanelType panelType : PanelType.values()) {
- if (TextUtils.equals(panelType.mId, id.toLowerCase())) {
- return panelType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to PanelType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- public Class<?> getPanelClass() {
- return mPanelClass;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<PanelType> CREATOR = new Creator<PanelType>() {
- @Override
- public PanelType createFromParcel(final Parcel source) {
- return PanelType.values()[source.readInt()];
- }
-
- @Override
- public PanelType[] newArray(final int size) {
- return new PanelType[size];
- }
- };
- }
-
- public static class PanelConfig implements Parcelable {
- private final PanelType mType;
- private final String mTitle;
- private final String mId;
- private final LayoutType mLayoutType;
- private final List<ViewConfig> mViews;
- private final AuthConfig mAuthConfig;
- private final EnumSet<Flags> mFlags;
- private final int mPosition;
-
- static final String JSON_KEY_TYPE = "type";
- static final String JSON_KEY_TITLE = "title";
- static final String JSON_KEY_ID = "id";
- static final String JSON_KEY_LAYOUT = "layout";
- static final String JSON_KEY_VIEWS = "views";
- static final String JSON_KEY_AUTH_CONFIG = "authConfig";
- static final String JSON_KEY_DEFAULT = "default";
- static final String JSON_KEY_DISABLED = "disabled";
- static final String JSON_KEY_POSITION = "position";
-
- public enum Flags {
- DEFAULT_PANEL,
- DISABLED_PANEL
- }
-
- public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- final String panelType = json.optString(JSON_KEY_TYPE, null);
- if (TextUtils.isEmpty(panelType)) {
- mType = PanelType.DYNAMIC;
- } else {
- mType = PanelType.fromId(panelType);
- }
-
- mTitle = json.getString(JSON_KEY_TITLE);
- mId = json.getString(JSON_KEY_ID);
-
- final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
- if (layoutTypeId != null) {
- mLayoutType = LayoutType.fromId(layoutTypeId);
- } else {
- mLayoutType = null;
- }
-
- final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
- if (jsonViews != null) {
- mViews = new ArrayList<ViewConfig>();
-
- final int viewCount = jsonViews.length();
- for (int i = 0; i < viewCount; i++) {
- final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
- final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig);
- mViews.add(viewConfig);
- }
- } else {
- mViews = null;
- }
-
- final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG);
- if (jsonAuthConfig != null) {
- mAuthConfig = new AuthConfig(jsonAuthConfig);
- } else {
- mAuthConfig = null;
- }
-
- mFlags = EnumSet.noneOf(Flags.class);
-
- if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
- mFlags.add(Flags.DEFAULT_PANEL);
- }
-
- if (json.optBoolean(JSON_KEY_DISABLED, false)) {
- mFlags.add(Flags.DISABLED_PANEL);
- }
-
- mPosition = json.optInt(JSON_KEY_POSITION, -1);
-
- validate();
- }
-
- @SuppressWarnings("unchecked")
- public PanelConfig(Parcel in) {
- mType = (PanelType) in.readParcelable(getClass().getClassLoader());
- mTitle = in.readString();
- mId = in.readString();
- mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
-
- mViews = new ArrayList<ViewConfig>();
- in.readTypedList(mViews, ViewConfig.CREATOR);
-
- mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
-
- mFlags = (EnumSet<Flags>) in.readSerializable();
- mPosition = in.readInt();
-
- validate();
- }
-
- public PanelConfig(PanelConfig panelConfig) {
- mType = panelConfig.mType;
- mTitle = panelConfig.mTitle;
- mId = panelConfig.mId;
- mLayoutType = panelConfig.mLayoutType;
-
- mViews = new ArrayList<ViewConfig>();
- List<ViewConfig> viewConfigs = panelConfig.mViews;
- if (viewConfigs != null) {
- for (ViewConfig viewConfig : viewConfigs) {
- mViews.add(new ViewConfig(viewConfig));
- }
- }
-
- mAuthConfig = panelConfig.mAuthConfig;
- mFlags = panelConfig.mFlags.clone();
- mPosition = panelConfig.mPosition;
-
- validate();
- }
-
- public PanelConfig(PanelType type, String title, String id) {
- this(type, title, id, EnumSet.noneOf(Flags.class));
- }
-
- public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
- this(type, title, id, null, null, null, flags, -1);
- }
-
- public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
- List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags, int position) {
- mType = type;
- mTitle = title;
- mId = id;
- mLayoutType = layoutType;
- mViews = views;
- mAuthConfig = authConfig;
- mFlags = flags;
- mPosition = position;
-
- validate();
- }
-
- private void validate() {
- if (mType == null) {
- throw new IllegalArgumentException("Can't create PanelConfig with null type");
- }
-
- if (TextUtils.isEmpty(mTitle)) {
- throw new IllegalArgumentException("Can't create PanelConfig with empty title");
- }
-
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("Can't create PanelConfig with empty id");
- }
-
- if (mLayoutType == null && mType == PanelType.DYNAMIC) {
- throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type");
- }
-
- if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) {
- throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views");
- }
-
- if (mFlags == null) {
- throw new IllegalArgumentException("Can't create PanelConfig with null flags");
- }
- }
-
- public PanelType getType() {
- return mType;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getId() {
- return mId;
- }
-
- public LayoutType getLayoutType() {
- return mLayoutType;
- }
-
- public int getViewCount() {
- return (mViews != null ? mViews.size() : 0);
- }
-
- public ViewConfig getViewAt(int index) {
- return (mViews != null ? mViews.get(index) : null);
- }
-
- public EnumSet<Flags> getFlags() {
- return mFlags.clone();
- }
-
- public boolean isDynamic() {
- return (mType == PanelType.DYNAMIC);
- }
-
- public boolean isDefault() {
- return mFlags.contains(Flags.DEFAULT_PANEL);
- }
-
- private void setIsDefault(boolean isDefault) {
- if (isDefault) {
- mFlags.add(Flags.DEFAULT_PANEL);
- } else {
- mFlags.remove(Flags.DEFAULT_PANEL);
- }
- }
-
- public boolean isDisabled() {
- return mFlags.contains(Flags.DISABLED_PANEL);
- }
-
- private void setIsDisabled(boolean isDisabled) {
- if (isDisabled) {
- mFlags.add(Flags.DISABLED_PANEL);
- } else {
- mFlags.remove(Flags.DISABLED_PANEL);
- }
- }
-
- public AuthConfig getAuthConfig() {
- return mAuthConfig;
- }
-
- public int getPosition() {
- return mPosition;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TYPE, mType.toString());
- json.put(JSON_KEY_TITLE, mTitle);
- json.put(JSON_KEY_ID, mId);
-
- if (mLayoutType != null) {
- json.put(JSON_KEY_LAYOUT, mLayoutType.toString());
- }
-
- if (mViews != null) {
- final JSONArray jsonViews = new JSONArray();
-
- final int viewCount = mViews.size();
- for (int i = 0; i < viewCount; i++) {
- final ViewConfig viewConfig = mViews.get(i);
- final JSONObject jsonViewConfig = viewConfig.toJSON();
- jsonViews.put(jsonViewConfig);
- }
-
- json.put(JSON_KEY_VIEWS, jsonViews);
- }
-
- if (mAuthConfig != null) {
- json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON());
- }
-
- if (mFlags.contains(Flags.DEFAULT_PANEL)) {
- json.put(JSON_KEY_DEFAULT, true);
- }
-
- if (mFlags.contains(Flags.DISABLED_PANEL)) {
- json.put(JSON_KEY_DISABLED, true);
- }
-
- json.put(JSON_KEY_POSITION, mPosition);
-
- return json;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
-
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof PanelConfig)) {
- return false;
- }
-
- final PanelConfig other = (PanelConfig) o;
- return mId.equals(other.mId);
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mType, 0);
- dest.writeString(mTitle);
- dest.writeString(mId);
- dest.writeParcelable(mLayoutType, 0);
- dest.writeTypedList(mViews);
- dest.writeParcelable(mAuthConfig, 0);
- dest.writeSerializable(mFlags);
- dest.writeInt(mPosition);
- }
-
- public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
- @Override
- public PanelConfig createFromParcel(final Parcel in) {
- return new PanelConfig(in);
- }
-
- @Override
- public PanelConfig[] newArray(final int size) {
- return new PanelConfig[size];
- }
- };
- }
-
- public static enum LayoutType implements Parcelable {
- FRAME("frame");
-
- private final String mId;
-
- LayoutType(String id) {
- mId = id;
- }
-
- public static LayoutType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to LayoutType");
- }
-
- for (LayoutType layoutType : LayoutType.values()) {
- if (TextUtils.equals(layoutType.mId, id.toLowerCase())) {
- return layoutType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to LayoutType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<LayoutType> CREATOR = new Creator<LayoutType>() {
- @Override
- public LayoutType createFromParcel(final Parcel source) {
- return LayoutType.values()[source.readInt()];
- }
-
- @Override
- public LayoutType[] newArray(final int size) {
- return new LayoutType[size];
- }
- };
- }
-
- public static enum ViewType implements Parcelable {
- LIST("list"),
- GRID("grid");
-
- private final String mId;
-
- ViewType(String id) {
- mId = id;
- }
-
- public static ViewType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ViewType");
- }
-
- for (ViewType viewType : ViewType.values()) {
- if (TextUtils.equals(viewType.mId, id.toLowerCase())) {
- return viewType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ViewType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ViewType> CREATOR = new Creator<ViewType>() {
- @Override
- public ViewType createFromParcel(final Parcel source) {
- return ViewType.values()[source.readInt()];
- }
-
- @Override
- public ViewType[] newArray(final int size) {
- return new ViewType[size];
- }
- };
- }
-
- public static enum ItemType implements Parcelable {
- ARTICLE("article"),
- IMAGE("image"),
- ICON("icon");
-
- private final String mId;
-
- ItemType(String id) {
- mId = id;
- }
-
- public static ItemType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ItemType");
- }
-
- for (ItemType itemType : ItemType.values()) {
- if (TextUtils.equals(itemType.mId, id.toLowerCase())) {
- return itemType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ItemType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ItemType> CREATOR = new Creator<ItemType>() {
- @Override
- public ItemType createFromParcel(final Parcel source) {
- return ItemType.values()[source.readInt()];
- }
-
- @Override
- public ItemType[] newArray(final int size) {
- return new ItemType[size];
- }
- };
- }
-
- public static enum ItemHandler implements Parcelable {
- BROWSER("browser"),
- INTENT("intent");
-
- private final String mId;
-
- ItemHandler(String id) {
- mId = id;
- }
-
- public static ItemHandler fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ItemHandler");
- }
-
- for (ItemHandler itemHandler : ItemHandler.values()) {
- if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) {
- return itemHandler;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ItemHandler");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ItemHandler> CREATOR = new Creator<ItemHandler>() {
- @Override
- public ItemHandler createFromParcel(final Parcel source) {
- return ItemHandler.values()[source.readInt()];
- }
-
- @Override
- public ItemHandler[] newArray(final int size) {
- return new ItemHandler[size];
- }
- };
- }
-
- public static class ViewConfig implements Parcelable {
- private final int mIndex;
- private final ViewType mType;
- private final String mDatasetId;
- private final ItemType mItemType;
- private final ItemHandler mItemHandler;
- private final String mBackImageUrl;
- private final String mFilter;
- private final EmptyViewConfig mEmptyViewConfig;
- private final HeaderConfig mHeaderConfig;
- private final EnumSet<Flags> mFlags;
-
- static final String JSON_KEY_TYPE = "type";
- static final String JSON_KEY_DATASET = "dataset";
- static final String JSON_KEY_ITEM_TYPE = "itemType";
- static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
- static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
- static final String JSON_KEY_FILTER = "filter";
- static final String JSON_KEY_EMPTY = "empty";
- static final String JSON_KEY_HEADER = "header";
- static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
-
- public enum Flags {
- REFRESH_ENABLED
- }
-
- public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
- mIndex = index;
- mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
- mDatasetId = json.getString(JSON_KEY_DATASET);
- mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
- mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
- mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null);
- mFilter = json.optString(JSON_KEY_FILTER, null);
-
- final JSONObject jsonEmptyViewConfig = json.optJSONObject(JSON_KEY_EMPTY);
- if (jsonEmptyViewConfig != null) {
- mEmptyViewConfig = new EmptyViewConfig(jsonEmptyViewConfig);
- } else {
- mEmptyViewConfig = null;
- }
-
- final JSONObject jsonHeaderConfig = json.optJSONObject(JSON_KEY_HEADER);
- mHeaderConfig = jsonHeaderConfig != null ? new HeaderConfig(jsonHeaderConfig) : null;
-
- mFlags = EnumSet.noneOf(Flags.class);
- if (json.optBoolean(JSON_KEY_REFRESH_ENABLED, false)) {
- mFlags.add(Flags.REFRESH_ENABLED);
- }
-
- validate();
- }
-
- @SuppressWarnings("unchecked")
- public ViewConfig(Parcel in) {
- mIndex = in.readInt();
- mType = (ViewType) in.readParcelable(getClass().getClassLoader());
- mDatasetId = in.readString();
- mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
- mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
- mBackImageUrl = in.readString();
- mFilter = in.readString();
- mEmptyViewConfig = (EmptyViewConfig) in.readParcelable(getClass().getClassLoader());
- mHeaderConfig = (HeaderConfig) in.readParcelable(getClass().getClassLoader());
- mFlags = (EnumSet<Flags>) in.readSerializable();
-
- validate();
- }
-
- public ViewConfig(ViewConfig viewConfig) {
- mIndex = viewConfig.mIndex;
- mType = viewConfig.mType;
- mDatasetId = viewConfig.mDatasetId;
- mItemType = viewConfig.mItemType;
- mItemHandler = viewConfig.mItemHandler;
- mBackImageUrl = viewConfig.mBackImageUrl;
- mFilter = viewConfig.mFilter;
- mEmptyViewConfig = viewConfig.mEmptyViewConfig;
- mHeaderConfig = viewConfig.mHeaderConfig;
- mFlags = viewConfig.mFlags.clone();
-
- validate();
- }
-
- private void validate() {
- if (mType == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null type");
- }
-
- if (TextUtils.isEmpty(mDatasetId)) {
- throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
- }
-
- if (mItemType == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null item type");
- }
-
- if (mItemHandler == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
- }
-
- if (mFlags == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null flags");
- }
- }
-
- public int getIndex() {
- return mIndex;
- }
-
- public ViewType getType() {
- return mType;
- }
-
- public String getDatasetId() {
- return mDatasetId;
- }
-
- public ItemType getItemType() {
- return mItemType;
- }
-
- public ItemHandler getItemHandler() {
- return mItemHandler;
- }
-
- public String getBackImageUrl() {
- return mBackImageUrl;
- }
-
- public String getFilter() {
- return mFilter;
- }
-
- public EmptyViewConfig getEmptyViewConfig() {
- return mEmptyViewConfig;
- }
-
- public HeaderConfig getHeaderConfig() {
- return mHeaderConfig;
- }
-
- public boolean hasHeaderConfig() {
- return mHeaderConfig != null;
- }
-
- public boolean isRefreshEnabled() {
- return mFlags.contains(Flags.REFRESH_ENABLED);
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TYPE, mType.toString());
- json.put(JSON_KEY_DATASET, mDatasetId);
- json.put(JSON_KEY_ITEM_TYPE, mItemType.toString());
- json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
-
- if (!TextUtils.isEmpty(mBackImageUrl)) {
- json.put(JSON_KEY_BACK_IMAGE_URL, mBackImageUrl);
- }
-
- if (!TextUtils.isEmpty(mFilter)) {
- json.put(JSON_KEY_FILTER, mFilter);
- }
-
- if (mEmptyViewConfig != null) {
- json.put(JSON_KEY_EMPTY, mEmptyViewConfig.toJSON());
- }
-
- if (mHeaderConfig != null) {
- json.put(JSON_KEY_HEADER, mHeaderConfig.toJSON());
- }
-
- if (mFlags.contains(Flags.REFRESH_ENABLED)) {
- json.put(JSON_KEY_REFRESH_ENABLED, true);
- }
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mIndex);
- dest.writeParcelable(mType, 0);
- dest.writeString(mDatasetId);
- dest.writeParcelable(mItemType, 0);
- dest.writeParcelable(mItemHandler, 0);
- dest.writeString(mBackImageUrl);
- dest.writeString(mFilter);
- dest.writeParcelable(mEmptyViewConfig, 0);
- dest.writeParcelable(mHeaderConfig, 0);
- dest.writeSerializable(mFlags);
- }
-
- public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
- @Override
- public ViewConfig createFromParcel(final Parcel in) {
- return new ViewConfig(in);
- }
-
- @Override
- public ViewConfig[] newArray(final int size) {
- return new ViewConfig[size];
- }
- };
- }
-
- public static class EmptyViewConfig implements Parcelable {
- private final String mText;
- private final String mImageUrl;
-
- static final String JSON_KEY_TEXT = "text";
- static final String JSON_KEY_IMAGE_URL = "imageUrl";
-
- public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- mText = json.optString(JSON_KEY_TEXT, null);
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
- }
-
- public EmptyViewConfig(Parcel in) {
- mText = in.readString();
- mImageUrl = in.readString();
- }
-
- public EmptyViewConfig(EmptyViewConfig emptyViewConfig) {
- mText = emptyViewConfig.mText;
- mImageUrl = emptyViewConfig.mImageUrl;
- }
-
- public EmptyViewConfig(String text, String imageUrl) {
- mText = text;
- mImageUrl = imageUrl;
- }
-
- public String getText() {
- return mText;
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TEXT, mText);
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mText);
- dest.writeString(mImageUrl);
- }
-
- public static final Creator<EmptyViewConfig> CREATOR = new Creator<EmptyViewConfig>() {
- @Override
- public EmptyViewConfig createFromParcel(final Parcel in) {
- return new EmptyViewConfig(in);
- }
-
- @Override
- public EmptyViewConfig[] newArray(final int size) {
- return new EmptyViewConfig[size];
- }
- };
- }
-
- public static class HeaderConfig implements Parcelable {
- static final String JSON_KEY_IMAGE_URL = "image_url";
- static final String JSON_KEY_URL = "url";
-
- private final String mImageUrl;
- private final String mUrl;
-
- public HeaderConfig(JSONObject json) {
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL);
- mUrl = json.optString(JSON_KEY_URL);
- }
-
- public HeaderConfig(Parcel in) {
- mImageUrl = in.readString();
- mUrl = in.readString();
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
- json.put(JSON_KEY_URL, mUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mImageUrl);
- dest.writeString(mUrl);
- }
-
- public static final Creator<HeaderConfig> CREATOR = new Creator<HeaderConfig>() {
- @Override
- public HeaderConfig createFromParcel(Parcel source) {
- return new HeaderConfig(source);
- }
-
- @Override
- public HeaderConfig[] newArray(int size) {
- return new HeaderConfig[size];
- }
- };
- }
-
- public static class AuthConfig implements Parcelable {
- private final String mMessageText;
- private final String mButtonText;
- private final String mImageUrl;
-
- static final String JSON_KEY_MESSAGE_TEXT = "messageText";
- static final String JSON_KEY_BUTTON_TEXT = "buttonText";
- static final String JSON_KEY_IMAGE_URL = "imageUrl";
-
- public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
- mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
- }
-
- public AuthConfig(Parcel in) {
- mMessageText = in.readString();
- mButtonText = in.readString();
- mImageUrl = in.readString();
-
- validate();
- }
-
- public AuthConfig(AuthConfig authConfig) {
- mMessageText = authConfig.mMessageText;
- mButtonText = authConfig.mButtonText;
- mImageUrl = authConfig.mImageUrl;
-
- validate();
- }
-
- public AuthConfig(String messageText, String buttonText, String imageUrl) {
- mMessageText = messageText;
- mButtonText = buttonText;
- mImageUrl = imageUrl;
-
- validate();
- }
-
- private void validate() {
- if (mMessageText == null) {
- throw new IllegalArgumentException("Can't create AuthConfig with null message text");
- }
-
- if (mButtonText == null) {
- throw new IllegalArgumentException("Can't create AuthConfig with null button text");
- }
- }
-
- public String getMessageText() {
- return mMessageText;
- }
-
- public String getButtonText() {
- return mButtonText;
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_MESSAGE_TEXT, mMessageText);
- json.put(JSON_KEY_BUTTON_TEXT, mButtonText);
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mMessageText);
- dest.writeString(mButtonText);
- dest.writeString(mImageUrl);
- }
-
- public static final Creator<AuthConfig> CREATOR = new Creator<AuthConfig>() {
- @Override
- public AuthConfig createFromParcel(final Parcel in) {
- return new AuthConfig(in);
- }
-
- @Override
- public AuthConfig[] newArray(final int size) {
- return new AuthConfig[size];
- }
- };
- }
- /**
- * Immutable representation of the current state of {@code HomeConfig}.
- * This is what HomeConfig returns from a load() call and takes as
- * input to save a new state.
- *
- * Users of {@code State} should use an {@code Iterator} to iterate
- * through the contained {@code PanelConfig} instances.
- *
- * {@code State} is immutable i.e. you can't add, remove, or update
- * contained elements directly. You have to use an {@code Editor} to
- * change the state, which can be created through the {@code edit()}
- * method.
- */
- public static class State implements Iterable<PanelConfig> {
- private HomeConfig mHomeConfig;
- private final List<PanelConfig> mPanelConfigs;
- private final boolean mIsDefault;
-
- State(List<PanelConfig> panelConfigs, boolean isDefault) {
- this(null, panelConfigs, isDefault);
- }
-
- private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs, boolean isDefault) {
- mHomeConfig = homeConfig;
- mPanelConfigs = Collections.unmodifiableList(panelConfigs);
- mIsDefault = isDefault;
- }
-
- private void setHomeConfig(HomeConfig homeConfig) {
- if (mHomeConfig != null) {
- throw new IllegalStateException("Can't set HomeConfig more than once");
- }
-
- mHomeConfig = homeConfig;
- }
-
- @Override
- public Iterator<PanelConfig> iterator() {
- return mPanelConfigs.iterator();
- }
-
- /**
- * Returns whether this {@code State} instance represents the default
- * {@code HomeConfig} configuration or not.
- */
- public boolean isDefault() {
- return mIsDefault;
- }
-
- /**
- * Creates an {@code Editor} for this state.
- */
- public Editor edit() {
- return new Editor(mHomeConfig, this);
- }
- }
-
- /**
- * {@code Editor} allows you to make changes to a {@code State}. You
- * can create {@code Editor} by calling {@code edit()} on the target
- * {@code State} instance.
- *
- * {@code Editor} works on a copy of the {@code State} that originated
- * it. This means that adding, removing, or updating panels in an
- * {@code Editor} will never change the {@code State} which you
- * created the {@code Editor} from. Calling {@code commit()} or
- * {@code apply()} will cause the new {@code State} instance to be
- * created and saved using the {@code HomeConfig} instance that
- * created the source {@code State}.
- *
- * {@code Editor} is *not* thread-safe. You can only make calls on it
- * from the thread where it was originally created. It will throw an
- * exception if you don't follow this invariant.
- */
- public static class Editor implements Iterable<PanelConfig> {
- private final HomeConfig mHomeConfig;
- private final Map<String, PanelConfig> mConfigMap;
- private final List<String> mConfigOrder;
- private final Thread mOriginalThread;
-
- // Each Pair represents parameters to a GeckoAppShell.notifyObservers call;
- // the first String is the observer topic and the second string is the notification data.
- private List<Pair<String, String>> mNotificationQueue;
- private PanelConfig mDefaultPanel;
- private int mEnabledCount;
-
- private boolean mHasChanged;
- private final boolean mIsFromDefault;
-
- private Editor(HomeConfig homeConfig, State configState) {
- mHomeConfig = homeConfig;
- mOriginalThread = Thread.currentThread();
- mConfigMap = new HashMap<String, PanelConfig>();
- mConfigOrder = new LinkedList<String>();
- mNotificationQueue = new ArrayList<>();
-
- mIsFromDefault = configState.isDefault();
-
- initFromState(configState);
- }
-
- /**
- * Initialize the initial state of the editor from the given
- * {@sode State}. A HashMap is used to represent the list of
- * panels as it provides fast access, and a LinkedList is used to
- * keep track of order. We keep a reference to the default panel
- * and the number of enabled panels to avoid iterating through the
- * map every time we need those.
- *
- * @param configState The source State to load the editor from.
- */
- private void initFromState(State configState) {
- for (PanelConfig panelConfig : configState) {
- final PanelConfig panelCopy = new PanelConfig(panelConfig);
-
- if (!panelCopy.isDisabled()) {
- mEnabledCount++;
- }
-
- if (panelCopy.isDefault()) {
- if (mDefaultPanel == null) {
- mDefaultPanel = panelCopy;
- } else {
- throw new IllegalStateException("Multiple default panels in HomeConfig state");
- }
- }
-
- final String panelId = panelConfig.getId();
- mConfigOrder.add(panelId);
- mConfigMap.put(panelId, panelCopy);
- }
-
- // We should always have a defined default panel if there's
- // at least one enabled panel around.
- if (mEnabledCount > 0 && mDefaultPanel == null) {
- throw new IllegalStateException("Default panel in HomeConfig state is undefined");
- }
- }
-
- private PanelConfig getPanelOrThrow(String panelId) {
- final PanelConfig panelConfig = mConfigMap.get(panelId);
- if (panelConfig == null) {
- throw new IllegalStateException("Tried to access non-existing panel: " + panelId);
- }
-
- return panelConfig;
- }
-
- private boolean isCurrentDefaultPanel(PanelConfig panelConfig) {
- if (mDefaultPanel == null) {
- return false;
- }
-
- return mDefaultPanel.equals(panelConfig);
- }
-
- private void findNewDefault() {
- // Pick the first panel that is neither disabled nor currently
- // set as default.
- for (PanelConfig panelConfig : makeOrderedCopy(false)) {
- if (!panelConfig.isDefault() && !panelConfig.isDisabled()) {
- setDefault(panelConfig.getId());
- return;
- }
- }
-
- mDefaultPanel = null;
- }
-
- /**
- * Makes an ordered list of PanelConfigs that can be references
- * or deep copied objects.
- *
- * @param deepCopy true to make deep-copied objects
- * @return ordered List of PanelConfigs
- */
- private List<PanelConfig> makeOrderedCopy(boolean deepCopy) {
- final List<PanelConfig> copiedList = new ArrayList<PanelConfig>(mConfigOrder.size());
- for (String panelId : mConfigOrder) {
- PanelConfig panelConfig = mConfigMap.get(panelId);
- if (deepCopy) {
- panelConfig = new PanelConfig(panelConfig);
- }
- copiedList.add(panelConfig);
- }
-
- return copiedList;
- }
-
- private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) {
- if (panelConfig.isDisabled() == disabled) {
- return;
- }
-
- panelConfig.setIsDisabled(disabled);
- mEnabledCount += (disabled ? -1 : 1);
- }
-
- /**
- * Gets the ID of the current default panel.
- */
- public String getDefaultPanelId() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (mDefaultPanel == null) {
- return null;
- }
-
- return mDefaultPanel.getId();
- }
-
- /**
- * Set a new default panel.
- *
- * @param panelId the ID of the new default panel.
- */
- public void setDefault(String panelId) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = getPanelOrThrow(panelId);
- if (isCurrentDefaultPanel(panelConfig)) {
- return;
- }
-
- if (mDefaultPanel != null) {
- mDefaultPanel.setIsDefault(false);
- }
-
- panelConfig.setIsDefault(true);
- setPanelIsDisabled(panelConfig, false);
-
- mDefaultPanel = panelConfig;
- mHasChanged = true;
- }
-
- /**
- * Toggles disabled state for a panel.
- *
- * @param panelId the ID of the target panel.
- * @param disabled true to disable the panel.
- */
- public void setDisabled(String panelId, boolean disabled) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = getPanelOrThrow(panelId);
- if (panelConfig.isDisabled() == disabled) {
- return;
- }
-
- setPanelIsDisabled(panelConfig, disabled);
-
- if (disabled) {
- if (isCurrentDefaultPanel(panelConfig)) {
- panelConfig.setIsDefault(false);
- findNewDefault();
- }
- } else if (mEnabledCount == 1) {
- setDefault(panelId);
- }
-
- mHasChanged = true;
- }
-
- /**
- * Adds a new {@code PanelConfig}. It will do nothing if the
- * {@code Editor} already contains a panel with the same ID.
- *
- * @param panelConfig the {@code PanelConfig} instance to be added.
- * @return true if the item has been added.
- */
- public boolean install(PanelConfig panelConfig) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (panelConfig == null) {
- throw new IllegalStateException("Can't install a null panel");
- }
-
- if (!panelConfig.isDynamic()) {
- throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId());
- }
-
- if (panelConfig.isDisabled()) {
- throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId());
- }
-
- boolean installed = false;
-
- final String id = panelConfig.getId();
- if (!mConfigMap.containsKey(id)) {
- mConfigMap.put(id, panelConfig);
-
- final int position = panelConfig.getPosition();
- if (position < 0 || position >= mConfigOrder.size()) {
- mConfigOrder.add(id);
- } else {
- mConfigOrder.add(position, id);
- }
-
- mEnabledCount++;
- if (mEnabledCount == 1 || panelConfig.isDefault()) {
- setDefault(panelConfig.getId());
- }
-
- installed = true;
-
- // Add an event to the queue if a new panel is successfully installed.
- mNotificationQueue.add(new Pair<String, String>(
- "HomePanels:Installed", panelConfig.getId()));
- }
-
- mHasChanged = true;
- return installed;
- }
-
- /**
- * Removes an existing panel.
- *
- * @return true if the item has been removed.
- */
- public boolean uninstall(String panelId) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = mConfigMap.get(panelId);
- if (panelConfig == null) {
- return false;
- }
-
- if (!panelConfig.isDynamic()) {
- throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId());
- }
-
- mConfigMap.remove(panelId);
- mConfigOrder.remove(panelId);
-
- if (!panelConfig.isDisabled()) {
- mEnabledCount--;
- }
-
- if (isCurrentDefaultPanel(panelConfig)) {
- findNewDefault();
- }
-
- // Add an event to the queue if a panel is successfully uninstalled.
- mNotificationQueue.add(new Pair<String, String>("HomePanels:Uninstalled", panelId));
-
- mHasChanged = true;
- return true;
- }
-
- /**
- * Moves panel associated with panelId to the specified position.
- *
- * @param panelId Id of panel
- * @param destIndex Destination position
- * @return true if move succeeded
- */
- public boolean moveTo(String panelId, int destIndex) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (!mConfigOrder.contains(panelId)) {
- return false;
- }
-
- mConfigOrder.remove(panelId);
- mConfigOrder.add(destIndex, panelId);
- mHasChanged = true;
-
- return true;
- }
-
- /**
- * Replaces an existing panel with a new {@code PanelConfig} instance.
- *
- * @return true if the item has been updated.
- */
- public boolean update(PanelConfig panelConfig) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (panelConfig == null) {
- throw new IllegalStateException("Can't update a null panel");
- }
-
- boolean updated = false;
-
- final String id = panelConfig.getId();
- if (mConfigMap.containsKey(id)) {
- final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig);
-
- // The disabled and default states can't never be
- // changed by an update operation.
- panelConfig.setIsDefault(oldPanelConfig.isDefault());
- panelConfig.setIsDisabled(oldPanelConfig.isDisabled());
-
- updated = true;
- }
-
- mHasChanged = true;
- return updated;
- }
-
- /**
- * Saves the current {@code Editor} state asynchronously in the
- * background thread.
- *
- * @return the resulting {@code State} instance.
- */
- public State apply() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- // We're about to save the current state in the background thread
- // so we should use a deep copy of the PanelConfig instances to
- // avoid saving corrupted state.
- final State newConfigState =
- new State(mHomeConfig, makeOrderedCopy(true), isDefault());
-
- // Copy the event queue to a new list, so that we only modify mNotificationQueue on
- // the original thread where it was created.
- final List<Pair<String, String>> copiedQueue = mNotificationQueue;
- mNotificationQueue = new ArrayList<>();
-
- ThreadUtils.getBackgroundHandler().post(new Runnable() {
- @Override
- public void run() {
- mHomeConfig.save(newConfigState);
-
- // Send pending events after the new config is saved.
- sendNotificationsToGecko(copiedQueue);
- }
- });
-
- return newConfigState;
- }
-
- /**
- * Saves the current {@code Editor} state synchronously in the
- * current thread.
- *
- * @return the resulting {@code State} instance.
- */
- public State commit() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final State newConfigState =
- new State(mHomeConfig, makeOrderedCopy(false), isDefault());
-
- // This is a synchronous blocking operation, hence no
- // need to deep copy the current PanelConfig instances.
- mHomeConfig.save(newConfigState);
-
- // Send pending events after the new config is saved.
- sendNotificationsToGecko(mNotificationQueue);
- mNotificationQueue.clear();
-
- return newConfigState;
- }
-
- /**
- * Returns whether the {@code Editor} represents the default
- * {@code HomeConfig} configuration without any unsaved changes.
- */
- public boolean isDefault() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- return (!mHasChanged && mIsFromDefault);
- }
-
- public boolean isEmpty() {
- return mConfigMap.isEmpty();
- }
-
- private void sendNotificationsToGecko(List<Pair<String, String>> notifications) {
- for (Pair<String, String> p : notifications) {
- GeckoAppShell.notifyObservers(p.first, p.second);
- }
- }
-
- private class EditorIterator implements Iterator<PanelConfig> {
- private final Iterator<String> mOrderIterator;
-
- public EditorIterator() {
- mOrderIterator = mConfigOrder.iterator();
- }
-
- @Override
- public boolean hasNext() {
- return mOrderIterator.hasNext();
- }
-
- @Override
- public PanelConfig next() {
- final String panelId = mOrderIterator.next();
- return mConfigMap.get(panelId);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Can't 'remove' from on Editor iterator.");
- }
- }
-
- @Override
- public Iterator<PanelConfig> iterator() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- return new EditorIterator();
- }
- }
-
- public interface OnReloadListener {
- public void onReload();
- }
-
- public interface HomeConfigBackend {
- public State load();
- public void save(State configState);
- public String getLocale();
- public void setOnReloadListener(OnReloadListener listener);
- }
-
- // UUIDs used to create PanelConfigs for default built-in panels. These are
- // public because they can be used in "about:home?panel=UUID" query strings
- // to open specific panels without querying the active Home Panel
- // configuration. Because they don't consider the active configuration, it
- // is only sensible to do this for built-in panels (and not for dynamic
- // panels).
- private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
- private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
- private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
- private static final String COMBINED_HISTORY_PANEL_ID = "4d716ce2-e063-486d-9e7c-b190d7b04dc6";
- private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
- private static final String REMOTE_TABS_PANEL_ID = "72429afd-8d8b-43d8-9189-14b779c563d0";
- private static final String DEPRECATED_READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
-
- private final HomeConfigBackend mBackend;
-
- public HomeConfig(HomeConfigBackend backend) {
- mBackend = backend;
- }
-
- public State load() {
- final State configState = mBackend.load();
- configState.setHomeConfig(this);
-
- return configState;
- }
-
- public String getLocale() {
- return mBackend.getLocale();
- }
-
- public void save(State configState) {
- mBackend.save(configState);
- }
-
- public void setOnReloadListener(OnReloadListener listener) {
- mBackend.setOnReloadListener(listener);
- }
-
- public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
- return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
- }
-
- public static int getTitleResourceIdForBuiltinPanelType(PanelType panelType) {
- switch (panelType) {
- case TOP_SITES:
- return R.string.home_top_sites_title;
-
- case BOOKMARKS:
- case DEPRECATED_READING_LIST:
- return R.string.bookmarks_title;
-
- case DEPRECATED_HISTORY:
- case DEPRECATED_REMOTE_TABS:
- case DEPRECATED_RECENT_TABS:
- case COMBINED_HISTORY:
- return R.string.home_history_title;
-
- default:
- throw new IllegalArgumentException("Only for built-in panel types: " + panelType);
- }
- }
-
- public static String getIdForBuiltinPanelType(PanelType panelType) {
- switch (panelType) {
- case TOP_SITES:
- return TOP_SITES_PANEL_ID;
-
- case BOOKMARKS:
- return BOOKMARKS_PANEL_ID;
-
- case DEPRECATED_HISTORY:
- return HISTORY_PANEL_ID;
-
- case COMBINED_HISTORY:
- return COMBINED_HISTORY_PANEL_ID;
-
- case DEPRECATED_REMOTE_TABS:
- return REMOTE_TABS_PANEL_ID;
-
- case DEPRECATED_READING_LIST:
- return DEPRECATED_READING_LIST_PANEL_ID;
-
- case DEPRECATED_RECENT_TABS:
- return RECENT_TABS_PANEL_ID;
-
- default:
- throw new IllegalArgumentException("Only for built-in panel types: " + panelType);
- }
- }
-
- public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
- final int titleId = getTitleResourceIdForBuiltinPanelType(panelType);
- final String id = getIdForBuiltinPanelType(panelType);
-
- return new PanelConfig(panelType, context.getString(titleId), id, flags);
- }
-
- public static HomeConfig getDefault(Context context) {
- return new HomeConfig(new HomeConfigPrefsBackend(context));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java
deleted file mode 100644
index 914d0fdd1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
-
-import android.content.Context;
-import android.support.v4.content.AsyncTaskLoader;
-
-public class HomeConfigLoader extends AsyncTaskLoader<HomeConfig.State> {
- private final HomeConfig mConfig;
- private HomeConfig.State mConfigState;
-
- private final Context mContext;
-
- public HomeConfigLoader(Context context, HomeConfig homeConfig) {
- super(context);
- mContext = context;
- mConfig = homeConfig;
- }
-
- @Override
- public HomeConfig.State loadInBackground() {
- return mConfig.load();
- }
-
- @Override
- public void deliverResult(HomeConfig.State configState) {
- if (isReset()) {
- mConfigState = null;
- return;
- }
-
- mConfigState = configState;
- mConfig.setOnReloadListener(new ForceReloadListener());
-
- if (isStarted()) {
- super.deliverResult(configState);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mConfigState != null) {
- deliverResult(mConfigState);
- }
-
- if (takeContentChanged() || mConfigState == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- public void onCanceled(HomeConfig.State configState) {
- mConfigState = null;
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped.
- onStopLoading();
-
- mConfigState = null;
- mConfig.setOnReloadListener(null);
- }
-
- private class ForceReloadListener implements OnReloadListener {
- @Override
- public void onReload() {
- onContentChanged();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
deleted file mode 100644
index a2d80788c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Locale;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
-import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.HomeConfig.State;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.support.annotation.CheckResult;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class HomeConfigPrefsBackend implements HomeConfigBackend {
- private static final String LOGTAG = "GeckoHomeConfigBackend";
-
- // Increment this to trigger a migration.
- @VisibleForTesting
- static final int VERSION = 8;
-
- // This key was originally used to store only an array of panel configs.
- public static final String PREFS_CONFIG_KEY_OLD = "home_panels";
-
- // This key is now used to store a version number with the array of panel configs.
- public static final String PREFS_CONFIG_KEY = "home_panels_with_version";
-
- // Keys used with JSON object stored in prefs.
- private static final String JSON_KEY_PANELS = "panels";
- private static final String JSON_KEY_VERSION = "version";
-
- private static final String PREFS_LOCALE_KEY = "home_locale";
-
- private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
-
- private final Context mContext;
- private ReloadBroadcastReceiver mReloadBroadcastReceiver;
- private OnReloadListener mReloadListener;
-
- private static boolean sMigrationDone;
-
- public HomeConfigPrefsBackend(Context context) {
- mContext = context;
- }
-
- private SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forProfile(mContext);
- }
-
- private State loadDefaultConfig() {
- final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
-
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
- EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
-
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.COMBINED_HISTORY));
-
-
- return new State(panelConfigs, true);
- }
-
- /**
- * Iterate through the panels to check if they are all disabled.
- */
- private static boolean allPanelsAreDisabled(JSONArray jsonPanels) throws JSONException {
- final int count = jsonPanels.length();
- for (int i = 0; i < count; i++) {
- final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
-
- if (!jsonPanelConfig.optBoolean(PanelConfig.JSON_KEY_DISABLED, false)) {
- return false;
- }
- }
-
- return true;
- }
-
- protected enum Position {
- NONE, // Not present.
- FRONT, // At the front of the list of panels.
- BACK, // At the back of the list of panels.
- }
-
- /**
- * Create and insert a built-in panel configuration.
- *
- * @param context Android context.
- * @param jsonPanels array of JSON panels to update in place.
- * @param panelType to add.
- * @param positionOnPhones where to place the new panel on phones.
- * @param positionOnTablets where to place the new panel on tablets.
- * @throws JSONException
- */
- protected static void addBuiltinPanelConfig(Context context, JSONArray jsonPanels,
- PanelType panelType, Position positionOnPhones, Position positionOnTablets) throws JSONException {
- // Add the new panel.
- final JSONObject jsonPanelConfig =
- createBuiltinPanelConfig(context, panelType).toJSON();
-
- // If any panel is enabled, then we should make the new panel enabled.
- jsonPanelConfig.put(PanelConfig.JSON_KEY_DISABLED,
- allPanelsAreDisabled(jsonPanels));
-
- final boolean isTablet = HardwareUtils.isTablet();
- final boolean isPhone = !isTablet;
-
- // Maybe add the new panel to the front of the array.
- if ((isPhone && positionOnPhones == Position.FRONT) ||
- (isTablet && positionOnTablets == Position.FRONT)) {
- // This is an inefficient way to stretch [a, b, c] to [a, a, b, c].
- for (int i = jsonPanels.length(); i >= 1; i--) {
- jsonPanels.put(i, jsonPanels.get(i - 1));
- }
- // And this inserts [d, a, b, c].
- jsonPanels.put(0, jsonPanelConfig);
- }
-
- // Maybe add the new panel to the back of the array.
- if ((isPhone && positionOnPhones == Position.BACK) ||
- (isTablet && positionOnTablets == Position.BACK)) {
- jsonPanels.put(jsonPanelConfig);
- }
- }
-
- /**
- * Updates the panels to combine the History and Sync panels into the (Combined) History panel.
- *
- * Tries to replace the History panel with the Combined History panel if visible, or falls back to
- * replacing the Sync panel if it's visible. That way, we minimize panel reordering during a migration.
- * @param context Android context
- * @param jsonPanels array of original JSON panels
- * @return new array of updated JSON panels
- * @throws JSONException
- */
- private static JSONArray combineHistoryAndSyncPanels(Context context, JSONArray jsonPanels) throws JSONException {
- EnumSet<PanelConfig.Flags> historyFlags = null;
- EnumSet<PanelConfig.Flags> syncFlags = null;
-
- int historyIndex = -1;
- int syncIndex = -1;
-
- // Determine state and location of History and Sync panels.
- for (int i = 0; i < jsonPanels.length(); i++) {
- JSONObject panelObj = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(panelObj);
- final PanelType type = panelConfig.getType();
- if (type == PanelType.DEPRECATED_HISTORY) {
- historyIndex = i;
- historyFlags = panelConfig.getFlags();
- } else if (type == PanelType.DEPRECATED_REMOTE_TABS) {
- syncIndex = i;
- syncFlags = panelConfig.getFlags();
- } else if (type == PanelType.COMBINED_HISTORY) {
- // Partial landing of bug 1220928 combined the History and Sync panels of users who didn't
- // have home panel customizations (including new users), thus they don't this migration.
- return jsonPanels;
- }
- }
-
- if (historyIndex == -1 || syncIndex == -1) {
- throw new IllegalArgumentException("Expected both History and Sync panels to be present prior to Combined History.");
- }
-
- PanelConfig newPanel;
- int replaceIndex;
- int removeIndex;
-
- if (historyFlags.contains(PanelConfig.Flags.DISABLED_PANEL) && !syncFlags.contains(PanelConfig.Flags.DISABLED_PANEL)) {
- // Replace the Sync panel if it's visible and the History panel is disabled.
- replaceIndex = syncIndex;
- removeIndex = historyIndex;
- newPanel = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, syncFlags);
- } else {
- // Otherwise, just replace the History panel.
- replaceIndex = historyIndex;
- removeIndex = syncIndex;
- newPanel = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, historyFlags);
- }
-
- // Copy the array with updated panel and removed panel.
- final JSONArray newArray = new JSONArray();
- for (int i = 0; i < jsonPanels.length(); i++) {
- if (i == replaceIndex) {
- newArray.put(newPanel.toJSON());
- } else if (i == removeIndex) {
- continue;
- } else {
- newArray.put(jsonPanels.get(i));
- }
- }
-
- return newArray;
- }
-
- /**
- * Iterate over all homepanels to verify that there is at least one default panel. If there is
- * no default panel, set History as the default panel. (This is only relevant for two botched
- * migrations where the history panel should have been made the default panel, but wasn't.)
- */
- private static void ensureDefaultPanelForV5orV8(Context context, JSONArray jsonPanels) throws JSONException {
- int historyIndex = -1;
-
- // If all panels are disabled, there is no default panel - this is the only valid state
- // that has no default. We can use this flag to track whether any visible panels have been
- // found.
- boolean enabledPanelsFound = false;
-
- for (int i = 0; i < jsonPanels.length(); i++) {
- final PanelConfig panelConfig = new PanelConfig(jsonPanels.getJSONObject(i));
- if (panelConfig.isDefault()) {
- return;
- }
-
- if (!panelConfig.isDisabled()) {
- enabledPanelsFound = true;
- }
-
- if (panelConfig.getType() == PanelType.COMBINED_HISTORY) {
- historyIndex = i;
- }
- }
-
- if (!enabledPanelsFound) {
- // No panels are enabled, hence there can be no default (see noEnabledPanelsFound declaration
- // for more information).
- return;
- }
-
- // Make the History panel default. We can't modify existing PanelConfigs, so make a new one.
- final PanelConfig historyPanelConfig = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL));
- jsonPanels.put(historyIndex, historyPanelConfig.toJSON());
- }
-
- /**
- * Removes a panel from the home panel config.
- * If the removed panel was set as the default home panel, we provide a replacement for it.
- *
- * @param context Android context
- * @param jsonPanels array of original JSON panels
- * @param panelToRemove The home panel to be removed.
- * @param replacementPanel The panel which will replace it if the removed panel
- * was the default home panel.
- * @param alwaysUnhide If true, the replacement panel will always be unhidden,
- * otherwise only if we turn it into the new default panel.
- * @return new array of updated JSON panels
- * @throws JSONException
- */
- private static JSONArray removePanel(Context context, JSONArray jsonPanels,
- PanelType panelToRemove, PanelType replacementPanel, boolean alwaysUnhide) throws JSONException {
- boolean wasDefault = false;
- boolean wasDisabled = false;
- int replacementPanelIndex = -1;
- boolean replacementWasDefault = false;
-
- // JSONArrary doesn't provide remove() for API < 19, therefore we need to manually copy all
- // the items we don't want deleted into a new array.
- final JSONArray newJSONPanels = new JSONArray();
-
- for (int i = 0; i < jsonPanels.length(); i++) {
- final JSONObject panelJSON = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(panelJSON);
-
- if (panelConfig.getType() == panelToRemove) {
- // If this panel was the default we'll need to assign a new default:
- wasDefault = panelConfig.isDefault();
- wasDisabled = panelConfig.isDisabled();
- } else {
- if (panelConfig.getType() == replacementPanel) {
- replacementPanelIndex = newJSONPanels.length();
- if (panelConfig.isDefault()) {
- replacementWasDefault = true;
- }
- }
-
- newJSONPanels.put(panelJSON);
- }
- }
-
- // Unless alwaysUnhide is true, we make the replacement panel visible only if it is going
- // to be the new default panel, since a hidden default panel doesn't make sense.
- // This is to allow preserving the behaviour of the original reading list migration function.
- if ((wasDefault || alwaysUnhide) && !wasDisabled) {
- final JSONObject replacementPanelConfig;
- if (wasDefault) {
- // If the removed panel was the default, the replacement has to be made the new default
- replacementPanelConfig = createBuiltinPanelConfig(context, replacementPanel, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)).toJSON();
- } else {
- final EnumSet<HomeConfig.PanelConfig.Flags> flags;
- if (replacementWasDefault) {
- // However if the replacement panel was already default, we need to preserve it's default status
- // (By rewriting the PanelConfig, we lose all existing flags, so we need to make sure desired
- // flags are retained - in this case there's only DEFAULT_PANEL, which is mutually
- // exclusive with the DISABLE_PANEL case).
- flags = EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL);
- } else {
- flags = EnumSet.noneOf(PanelConfig.Flags.class);
- }
-
- // The panel is visible since we don't set Flags.DISABLED_PANEL.
- replacementPanelConfig = createBuiltinPanelConfig(context, replacementPanel, flags).toJSON();
- }
-
- if (replacementPanelIndex != -1) {
- newJSONPanels.put(replacementPanelIndex, replacementPanelConfig);
- } else {
- newJSONPanels.put(replacementPanelConfig);
- }
- }
-
- return newJSONPanels;
- }
-
- /**
- * Checks to see if the reading list panel already exists.
- *
- * @param jsonPanels JSONArray array representing the curent set of panel configs.
- *
- * @return boolean Whether or not the reading list panel exists.
- */
- private static boolean readingListPanelExists(JSONArray jsonPanels) {
- final int count = jsonPanels.length();
- for (int i = 0; i < count; i++) {
- try {
- final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
- if (panelConfig.getType() == PanelType.DEPRECATED_READING_LIST) {
- return true;
- }
- } catch (Exception e) {
- // It's okay to ignore this exception, since an invalid reading list
- // panel config is equivalent to no reading list panel.
- Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
- }
- }
- return false;
- }
-
- @CheckResult
- static synchronized JSONArray migratePrefsFromVersionToVersion(final Context context, final int currentVersion, final int newVersion,
- final JSONArray jsonPanelsIn, final SharedPreferences.Editor prefsEditor) throws JSONException {
-
- JSONArray jsonPanels = jsonPanelsIn;
-
- for (int v = currentVersion + 1; v <= newVersion; v++) {
- Log.d(LOGTAG, "Migrating to version = " + v);
-
- switch (v) {
- case 1:
- // Add "Recent Tabs" panel.
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_RECENT_TABS, Position.FRONT, Position.BACK);
-
- // Remove the old pref key.
- prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
- break;
-
- case 2:
- // Add "Remote Tabs"/"Synced Tabs" panel.
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_REMOTE_TABS, Position.FRONT, Position.BACK);
- break;
-
- case 3:
- // Add the "Reading List" panel if it does not exist. At one time,
- // the Reading List panel was shown only to devices that were not
- // considered "low memory". Now, we expose the panel to all devices.
- // This migration should only occur for "low memory" devices.
- // Note: This will not agree with the default configuration, which
- // has DEPRECATED_REMOTE_TABS after DEPRECATED_READING_LIST on some devices.
- if (!readingListPanelExists(jsonPanels)) {
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_READING_LIST, Position.BACK, Position.BACK);
- }
- break;
-
- case 4:
- // Combine the History and Sync panels. In order to minimize an unexpected reordering
- // of panels, we try to replace the History panel if it's visible, and fall back to
- // the Sync panel if that's visible.
- jsonPanels = combineHistoryAndSyncPanels(context, jsonPanels);
- break;
-
- case 5:
- // This is the fix for bug 1264136 where we lost track of the default panel during some migrations.
- ensureDefaultPanelForV5orV8(context, jsonPanels);
- break;
-
- case 6:
- jsonPanels = removePanel(context, jsonPanels,
- PanelType.DEPRECATED_READING_LIST, PanelType.BOOKMARKS, false);
- break;
-
- case 7:
- jsonPanels = removePanel(context, jsonPanels,
- PanelType.DEPRECATED_RECENT_TABS, PanelType.COMBINED_HISTORY, true);
- break;
-
- case 8:
- // Similar to "case 5" above, this time 1304777 - once again we lost track
- // of the history panel
- ensureDefaultPanelForV5orV8(context, jsonPanels);
- break;
- }
- }
-
- return jsonPanels;
- }
-
- /**
- * Migrates JSON config data storage.
- *
- * @param context Context used to get shared preferences and create built-in panel.
- * @param jsonString String currently stored in preferences.
- *
- * @return JSONArray array representing new set of panel configs.
- */
- private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
- // If the migration is already done, we're at the current version.
- if (sMigrationDone) {
- final JSONObject json = new JSONObject(jsonString);
- return json.getJSONArray(JSON_KEY_PANELS);
- }
-
- // Make sure we only do this version check once.
- sMigrationDone = true;
-
- JSONArray jsonPanels;
- final int version;
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
- if (prefs.contains(PREFS_CONFIG_KEY_OLD)) {
- // Our original implementation did not contain versioning, so this is implicitly version 0.
- jsonPanels = new JSONArray(jsonString);
- version = 0;
- } else {
- final JSONObject json = new JSONObject(jsonString);
- jsonPanels = json.getJSONArray(JSON_KEY_PANELS);
- version = json.getInt(JSON_KEY_VERSION);
- }
-
- if (version == VERSION) {
- return jsonPanels;
- }
-
- Log.d(LOGTAG, "Performing migration");
-
- final SharedPreferences.Editor prefsEditor = prefs.edit();
-
- jsonPanels = migratePrefsFromVersionToVersion(context, version, VERSION, jsonPanels, prefsEditor);
-
- // Save the new panel config and the new version number.
- final JSONObject newJson = new JSONObject();
- newJson.put(JSON_KEY_PANELS, jsonPanels);
- newJson.put(JSON_KEY_VERSION, VERSION);
-
- prefsEditor.putString(PREFS_CONFIG_KEY, newJson.toString());
- prefsEditor.apply();
-
- return jsonPanels;
- }
-
- private State loadConfigFromString(String jsonString) {
- final JSONArray jsonPanelConfigs;
- try {
- jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
- updatePrefsFromConfig(jsonPanelConfigs);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
-
- // Fallback to default config
- return loadDefaultConfig();
- }
-
- final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
-
- final int count = jsonPanelConfigs.length();
- for (int i = 0; i < count; i++) {
- try {
- final JSONObject jsonPanelConfig = jsonPanelConfigs.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
- panelConfigs.add(panelConfig);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
- }
- }
-
- return new State(panelConfigs, false);
- }
-
- @Override
- public State load() {
- final SharedPreferences prefs = getSharedPreferences();
-
- final String key = (prefs.contains(PREFS_CONFIG_KEY_OLD) ? PREFS_CONFIG_KEY_OLD : PREFS_CONFIG_KEY);
- final String jsonString = prefs.getString(key, null);
-
- final State configState;
- if (TextUtils.isEmpty(jsonString)) {
- configState = loadDefaultConfig();
- } else {
- configState = loadConfigFromString(jsonString);
- }
-
- return configState;
- }
-
- @Override
- public void save(State configState) {
- final SharedPreferences prefs = getSharedPreferences();
- final SharedPreferences.Editor editor = prefs.edit();
-
- // No need to save the state to disk if it represents the default
- // HomeConfig configuration. Simply force all existing HomeConfigLoader
- // instances to refresh their contents.
- if (!configState.isDefault()) {
- final JSONArray jsonPanelConfigs = new JSONArray();
-
- for (PanelConfig panelConfig : configState) {
- try {
- final JSONObject jsonPanelConfig = panelConfig.toJSON();
- jsonPanelConfigs.put(jsonPanelConfig);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
- }
- }
-
- try {
- final JSONObject json = new JSONObject();
- json.put(JSON_KEY_PANELS, jsonPanelConfigs);
- json.put(JSON_KEY_VERSION, VERSION);
-
- editor.putString(PREFS_CONFIG_KEY, json.toString());
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception saving PanelConfig state", e);
- }
- }
-
- editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
- editor.apply();
-
- // Trigger reload listeners on all live backend instances
- sendReloadBroadcast();
- }
-
- @Override
- public String getLocale() {
- final SharedPreferences prefs = getSharedPreferences();
-
- String locale = prefs.getString(PREFS_LOCALE_KEY, null);
- if (locale == null) {
- // Initialize config with the current locale
- final String currentLocale = Locale.getDefault().toString();
-
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putString(PREFS_LOCALE_KEY, currentLocale);
- editor.apply();
-
- // If the user has saved HomeConfig before, return null this
- // one time to trigger a refresh and ensure we use the
- // correct locale for the saved state. For more context,
- // see HomePanelsManager.onLocaleReady().
- if (!prefs.contains(PREFS_CONFIG_KEY)) {
- locale = currentLocale;
- }
- }
-
- return locale;
- }
-
- @Override
- public void setOnReloadListener(OnReloadListener listener) {
- if (mReloadListener != null) {
- unregisterReloadReceiver();
- mReloadBroadcastReceiver = null;
- }
-
- mReloadListener = listener;
-
- if (mReloadListener != null) {
- mReloadBroadcastReceiver = new ReloadBroadcastReceiver();
- registerReloadReceiver();
- }
- }
-
- /**
- * Update prefs that depend on home panels state.
- *
- * This includes the prefs that keep track of whether bookmarks or history are enabled, which are
- * used to control the visibility of the corresponding menu items.
- */
- private void updatePrefsFromConfig(JSONArray panelsArray) {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(mContext);
- if (!prefs.contains(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED)
- || !prefs.contains(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED)) {
-
- final String bookmarkType = PanelType.BOOKMARKS.toString();
- final String historyType = PanelType.COMBINED_HISTORY.toString();
- try {
- for (int i = 0; i < panelsArray.length(); i++) {
- final JSONObject panelObj = panelsArray.getJSONObject(i);
- final String panelType = panelObj.optString(PanelConfig.JSON_KEY_TYPE, null);
- if (panelType == null) {
- break;
- }
- final boolean isDisabled = panelObj.optBoolean(PanelConfig.JSON_KEY_DISABLED, false);
- if (bookmarkType.equals(panelType)) {
- prefs.edit().putBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, !isDisabled).apply();
- } else if (historyType.equals(panelType)) {
- prefs.edit().putBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, !isDisabled).apply();
- }
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error fetching panel from config to update prefs");
- }
- }
- }
-
-
- private void sendReloadBroadcast() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- final Intent reloadIntent = new Intent(RELOAD_BROADCAST);
- lbm.sendBroadcast(reloadIntent);
- }
-
- private void registerReloadReceiver() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- lbm.registerReceiver(mReloadBroadcastReceiver, new IntentFilter(RELOAD_BROADCAST));
- }
-
- private void unregisterReloadReceiver() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- lbm.unregisterReceiver(mReloadBroadcastReceiver);
- }
-
- private class ReloadBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- mReloadListener.onReload();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
deleted file mode 100644
index cefa0329d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.ExpandableListAdapter;
-import android.widget.ListAdapter;
-
-/**
- * A ContextMenuInfo for HomeListView
- */
-public class HomeContextMenuInfo extends AdapterContextMenuInfo {
-
- public String url;
- public String title;
- public boolean isFolder;
- public int historyId = -1;
- public int bookmarkId = -1;
- public RemoveItemType itemType = null;
-
- // Item type to be handled with "Remove" selection.
- public static enum RemoveItemType {
- BOOKMARKS, COMBINED, HISTORY
- }
-
- public HomeContextMenuInfo(View targetView, int position, long id) {
- super(targetView, position, id);
- }
-
- public boolean hasBookmarkId() {
- return bookmarkId > -1;
- }
-
- public boolean hasHistoryId() {
- return historyId > -1;
- }
-
- public boolean hasPartnerBookmarkId() {
- return bookmarkId <= BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START;
- }
-
- public boolean canRemove() {
- return hasBookmarkId() || hasHistoryId() || hasPartnerBookmarkId();
- }
-
- public String getDisplayTitle() {
- if (!TextUtils.isEmpty(title)) {
- return title;
- }
- return StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from cursors.
- */
- public interface Factory {
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor);
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from ListAdapters.
- */
- public interface ListFactory extends Factory {
- public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ListAdapter adapter);
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from ExpandableListAdapters.
- */
- public interface ExpandableFactory {
- public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ExpandableListAdapter adapter);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
deleted file mode 100644
index 7badd6929..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ExpandableListView;
-
-/**
- * <code>HomeExpandableListView</code> is a custom extension of
- * <code>ExpandableListView<code>, that packs a <code>HomeContextMenuInfo</code>
- * when any of its rows is long pressed.
- * <p>
- * This is the <code>ExpandableListView</code> equivalent of
- * <code>HomeListView</code>.
- */
-public class HomeExpandableListView extends ExpandableListView
- implements OnItemLongClickListener {
-
- // ContextMenuInfo associated with the currently long pressed list item.
- private HomeContextMenuInfo mContextMenuInfo;
-
- // ContextMenuInfo factory.
- private HomeContextMenuInfo.ExpandableFactory mContextMenuInfoFactory;
-
- public HomeExpandableListView(Context context) {
- this(context, null);
- }
-
- public HomeExpandableListView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public HomeExpandableListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- setOnItemLongClickListener(this);
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- if (mContextMenuInfoFactory == null) {
- return false;
- }
-
- // HomeExpandableListView items can correspond to groups and children.
- // The factory can determine whether to add context menu for either,
- // both, or none by unpacking the given position.
- mContextMenuInfo = mContextMenuInfoFactory.makeInfoForAdapter(view, position, id, getExpandableListAdapter());
- return showContextMenuForChild(HomeExpandableListView.this);
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- public void setContextMenuInfoFactory(final HomeContextMenuInfo.ExpandableFactory factory) {
- mContextMenuInfoFactory = factory;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
deleted file mode 100644
index da6e9b703..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.EditBookmarkDialog;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.IntentHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.reader.ReadingListHelper;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.Fragment;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-/**
- * HomeFragment is an empty fragment that can be added to the HomePager.
- * Subclasses can add their own views.
- * <p>
- * The containing activity <b>must</b> implement {@link OnUrlOpenListener}.
- */
-public abstract class HomeFragment extends Fragment {
- // Log Tag.
- private static final String LOGTAG = "GeckoHomeFragment";
-
- // Share MIME type.
- protected static final String SHARE_MIME_TYPE = "text/plain";
-
- // Default value for "can load" hint
- static final boolean DEFAULT_CAN_LOAD_HINT = false;
-
- // Whether the fragment can load its content or not
- // This is used to defer data loading until the editing
- // mode animation ends.
- private boolean mCanLoadHint;
-
- // Whether the fragment has loaded its content
- private boolean mIsLoaded;
-
- // On URL open listener
- protected OnUrlOpenListener mUrlOpenListener;
-
- // Helper for opening a tab in the background.
- protected OnUrlOpenInBackgroundListener mUrlOpenInBackgroundListener;
-
- protected PanelStateChangeListener mPanelStateChangeListener = null;
-
- /**
- * Listener to notify when a home panels' state has changed in a way that needs to be stored
- * for history/restoration. E.g. when a folder is opened/closed in bookmarks.
- */
- public interface PanelStateChangeListener {
-
- /**
- * @param bundle Data that should be persisted, and passed to this panel if restored at a later
- * stage.
- */
- void onStateChanged(Bundle bundle);
-
- void setCachedRecentTabsCount(int count);
-
- int getCachedRecentTabsCount();
- }
-
- public void restoreData(Bundle data) {
- // Do nothing
- }
-
- public void setPanelStateChangeListener(
- PanelStateChangeListener mPanelStateChangeListener) {
- this.mPanelStateChangeListener = mPanelStateChangeListener;
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- mUrlOpenListener = (OnUrlOpenListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement HomePager.OnUrlOpenListener");
- }
-
- try {
- mUrlOpenInBackgroundListener = (OnUrlOpenInBackgroundListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement HomePager.OnUrlOpenInBackgroundListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mUrlOpenListener = null;
- mUrlOpenInBackgroundListener = null;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle args = getArguments();
- if (args != null) {
- mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, DEFAULT_CAN_LOAD_HINT);
- } else {
- mCanLoadHint = DEFAULT_CAN_LOAD_HINT;
- }
-
- mIsLoaded = false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return;
- }
-
- HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
-
- // Don't show the context menu for folders.
- if (info.isFolder) {
- return;
- }
-
- MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.home_contextmenu, menu);
-
- menu.setHeaderTitle(info.getDisplayTitle());
-
- // Hide unused menu items.
- menu.findItem(R.id.top_sites_edit).setVisible(false);
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
-
- // Hide the "Edit" menuitem if this item isn't a bookmark,
- // or if this is a reading list item.
- if (!info.hasBookmarkId()) {
- menu.findItem(R.id.home_edit_bookmark).setVisible(false);
- }
-
- // Hide the "Remove" menuitem if this item not removable.
- if (!info.canRemove()) {
- menu.findItem(R.id.home_remove).setVisible(false);
- }
-
- if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
- menu.findItem(R.id.home_share).setVisible(false);
- }
-
- if (!Restrictions.isAllowed(view.getContext(), Restrictable.PRIVATE_BROWSING)) {
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- // onContextItemSelected() is first dispatched to the activity and
- // then dispatched to its fragments. Since fragments cannot "override"
- // menu item selection handling, it's better to avoid menu id collisions
- // between the activity and its fragments.
-
- ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return false;
- }
-
- final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
- final Context context = getActivity();
-
- final int itemId = item.getItemId();
-
- // Track the menu action. We don't know much about the context, but we can use this to determine
- // the frequency of use for various actions.
- String extras = getResources().getResourceEntryName(itemId);
- if (TextUtils.equals(extras, "home_open_private_tab")) {
- // Mask private browsing
- extras = "home_open_new_tab";
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, extras);
-
- if (itemId == R.id.home_copyurl) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't copy address because URL is null");
- return false;
- }
-
- Clipboard.setText(info.url);
- return true;
- }
-
- if (itemId == R.id.home_share) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't share because URL is null");
- return false;
- } else {
- IntentHelper.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
- Intent.ACTION_SEND, info.getDisplayTitle(), false);
-
- // Context: Sharing via chrome homepage contextmenu list (home session should be active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "home_contextmenu");
- return true;
- }
- }
-
- if (itemId == R.id.home_add_to_launcher) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't add to home screen because URL is null");
- return false;
- }
-
- // Fetch an icon big enough for use as a home screen icon.
- final String displayTitle = info.getDisplayTitle();
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.createShortcut(displayTitle, info.url);
-
- }
- });
-
- return true;
- }
-
- if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't open in new tab because URL is null");
- return false;
- }
-
- // Some pinned site items have "user-entered" urls. URLs entered in
- // the PinSiteDialog are wrapped in a special URI until we can get a
- // valid URL. If the url is a user-entered url, decode the URL
- // before loading it.
- final String url = StringUtils.decodeUserEnteredUrl(info.url);
-
- final EnumSet<OnUrlOpenInBackgroundListener.Flags> flags = EnumSet.noneOf(OnUrlOpenInBackgroundListener.Flags.class);
- if (item.getItemId() == R.id.home_open_private_tab) {
- flags.add(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
- }
-
- mUrlOpenInBackgroundListener.onUrlOpenInBackground(url, flags);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
-
- return true;
- }
-
- if (itemId == R.id.home_edit_bookmark) {
- // UI Dialog associates to the activity context, not the applications'.
- new EditBookmarkDialog(context).show(info.url);
- return true;
- }
-
- if (itemId == R.id.home_remove) {
- // For Top Sites grid items, position is required in case item is Pinned.
- final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
-
- if (info.hasPartnerBookmarkId()) {
- new RemovePartnerBookmarkTask(context, info.bookmarkId).execute();
- } else {
- new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
- }
- return true;
- }
-
- return false;
- }
-
- @Override
- public void setUserVisibleHint (boolean isVisibleToUser) {
- if (isVisibleToUser == getUserVisibleHint()) {
- return;
- }
-
- super.setUserVisibleHint(isVisibleToUser);
- loadIfVisible();
- }
-
- /**
- * Handle a configuration change by detaching and re-attaching.
- * <p>
- * A HomeFragment only needs to handle onConfiguration change (i.e.,
- * re-attach) if its UI needs to change (i.e., re-inflate layouts, use
- * different styles, etc) for different device orientations. Handling
- * configuration changes in all HomeFragments will simply cause some
- * redundant re-inflations on device rotation. This slight inefficiency
- * avoids potentially not handling a needed onConfigurationChanged in a
- * subclass.
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- // Reattach the fragment, forcing a re-inflation of its view.
- // We use commitAllowingStateLoss() instead of commit() here to avoid
- // an IllegalStateException. If the phone is rotated while Fennec
- // is in the background, onConfigurationChanged() is fired.
- // onConfigurationChanged() is called before onResume(), so
- // using commit() would throw an IllegalStateException since it can't
- // be used between the Activity's onSaveInstanceState() and
- // onResume().
- if (isVisible()) {
- getFragmentManager().beginTransaction()
- .detach(this)
- .attach(this)
- .commitAllowingStateLoss();
- }
- }
-
- void setCanLoadHint(boolean canLoadHint) {
- if (mCanLoadHint == canLoadHint) {
- return;
- }
-
- mCanLoadHint = canLoadHint;
- loadIfVisible();
- }
-
- boolean getCanLoadHint() {
- return mCanLoadHint;
- }
-
- protected abstract void load();
-
- protected boolean canLoad() {
- return (mCanLoadHint && isVisible() && getUserVisibleHint());
- }
-
- protected void loadIfVisible() {
- if (!canLoad() || mIsLoaded) {
- return;
- }
-
- load();
- mIsLoaded = true;
- }
-
- protected static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
- private final Context mContext;
- private final String mUrl;
- private final RemoveItemType mType;
- private final int mPosition;
- private final BrowserDB mDB;
-
- /**
- * Remove bookmark/history/reading list type item by url, and also unpin the
- * Top Sites grid item at index <code>position</code>.
- */
- public RemoveItemByUrlTask(Context context, String url, RemoveItemType type, int position) {
- super(ThreadUtils.getBackgroundHandler());
-
- mContext = context;
- mUrl = url;
- mType = type;
- mPosition = position;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Void doInBackground() {
- ContentResolver cr = mContext.getContentResolver();
-
- if (mPosition > -1) {
- mDB.unpinSite(cr, mPosition);
- if (mDB.hideSuggestedSite(mUrl)) {
- cr.notifyChange(SuggestedSites.CONTENT_URI, null);
- }
- }
-
- switch (mType) {
- case BOOKMARKS:
- removeBookmark(cr);
- break;
-
- case HISTORY:
- removeHistory(cr);
- break;
-
- case COMBINED:
- removeBookmark(cr);
- removeHistory(cr);
- break;
-
- default:
- Log.e(LOGTAG, "Can't remove item type " + mType.toString());
- break;
- }
- return null;
- }
-
- @Override
- public void onPostExecute(Void result) {
- SnackbarBuilder.builder((Activity) mContext)
- .message(R.string.page_removed)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
-
- private void removeBookmark(ContentResolver cr) {
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
- final boolean isReaderViewPage = rch.isURLCached(mUrl);
-
- final String extra;
- if (isReaderViewPage) {
- extra = "bookmark_reader";
- } else {
- extra = "bookmark";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, extra);
- mDB.removeBookmarksWithURL(cr, mUrl);
-
- if (isReaderViewPage) {
- ReadingListHelper.removeCachedReaderItem(mUrl, mContext);
- }
- }
-
- private void removeHistory(ContentResolver cr) {
- mDB.removeHistoryEntry(cr, mUrl);
- }
- }
-
- private static class RemovePartnerBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
- private Context context;
- private long bookmarkId;
-
- public RemovePartnerBookmarkTask(Context context, long bookmarkId) {
- super(ThreadUtils.getBackgroundHandler());
-
- this.context = context;
- this.bookmarkId = bookmarkId;
- }
-
- @Override
- protected Void doInBackground() {
- context.getContentResolver().delete(
- PartnerBookmarksProviderProxy.getUriForBookmark(context, bookmarkId),
- null,
- null
- );
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- SnackbarBuilder.builder((Activity) context)
- .message(R.string.page_removed)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java
deleted file mode 100644
index d179a27ce..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ListView;
-
-/**
- * HomeListView is a custom extension of ListView, that packs a HomeContextMenuInfo
- * when any of its rows is long pressed.
- */
-public class HomeListView extends ListView
- implements OnItemLongClickListener {
-
- // ContextMenuInfo associated with the currently long pressed list item.
- private HomeContextMenuInfo mContextMenuInfo;
-
- // On URL open listener
- protected OnUrlOpenListener mUrlOpenListener;
-
- // Top divider
- private final boolean mShowTopDivider;
-
- // ContextMenuInfo maker
- private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
-
- public HomeListView(Context context) {
- this(context, null);
- }
-
- public HomeListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.homeListViewStyle);
- }
-
- public HomeListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HomeListView, defStyle, 0);
- mShowTopDivider = a.getBoolean(R.styleable.HomeListView_topDivider, false);
- a.recycle();
-
- setOnItemLongClickListener(this);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final Drawable divider = getDivider();
- if (mShowTopDivider && divider != null) {
- final int dividerHeight = getDividerHeight();
- final View view = new View(getContext());
- view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dividerHeight));
- addHeaderView(view);
- }
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- mUrlOpenListener = null;
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- Object item = parent.getItemAtPosition(position);
-
- // HomeListView could hold headers too. Add a context menu info only for its children.
- if (item instanceof Cursor) {
- Cursor cursor = (Cursor) item;
- if (cursor == null || mContextMenuInfoFactory == null) {
- mContextMenuInfo = null;
- return false;
- }
-
- mContextMenuInfo = mContextMenuInfoFactory.makeInfoForCursor(view, position, id, cursor);
- return showContextMenuForChild(HomeListView.this);
-
- } else if (mContextMenuInfoFactory instanceof HomeContextMenuInfo.ListFactory) {
- mContextMenuInfo = ((HomeContextMenuInfo.ListFactory) mContextMenuInfoFactory).makeInfoForAdapter(view, position, id, getAdapter());
- return showContextMenuForChild(HomeListView.this);
- } else {
- mContextMenuInfo = null;
- return false;
- }
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- @Override
- public void setOnItemClickListener(final AdapterView.OnItemClickListener listener) {
- if (listener == null) {
- super.setOnItemClickListener(null);
- return;
- }
-
- super.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mShowTopDivider) {
- position--;
- }
-
- listener.onItemClick(parent, view, position, id);
- }
- });
- }
-
- public void setContextMenuInfoFactory(final HomeContextMenuInfo.Factory factory) {
- mContextMenuInfoFactory = factory;
- }
-
- public OnUrlOpenListener getOnUrlOpenListener() {
- return mUrlOpenListener;
- }
-
- public void setOnUrlOpenListener(OnUrlOpenListener listener) {
- mUrlOpenListener = listener;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java b/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
deleted file mode 100644
index 4915f0c91..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class HomePager extends ViewPager implements HomeScreen {
-
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- return super.requestFocus(direction, previouslyFocusedRect);
- }
-
- private static final int LOADER_ID_CONFIG = 0;
-
- private final Context mContext;
- private volatile boolean mVisible;
- private Decor mDecor;
- private View mTabStrip;
- private HomeBanner mHomeBanner;
- private int mDefaultPageIndex = -1;
-
- private final OnAddPanelListener mAddPanelListener;
-
- private final HomeConfig mConfig;
- private final ConfigLoaderCallbacks mConfigLoaderCallbacks;
-
- private String mInitialPanelId;
- private Bundle mRestoreData;
-
- // Cached original ViewPager background.
- private final Drawable mOriginalBackground;
-
- // Telemetry session for current panel.
- private TelemetryContract.Session mCurrentPanelSession;
- private String mCurrentPanelSessionSuffix;
-
- // Current load state of HomePager.
- private LoadState mLoadState;
-
- // Listens for when the current panel changes.
- private OnPanelChangeListener mPanelChangedListener;
-
- private HomeFragment.PanelStateChangeListener mPanelStateChangeListener;
-
- // This is mostly used by UI tests to easily fetch
- // specific list views at runtime.
- public static final String LIST_TAG_HISTORY = "history";
- public static final String LIST_TAG_BOOKMARKS = "bookmarks";
- public static final String LIST_TAG_TOP_SITES = "top_sites";
- public static final String LIST_TAG_RECENT_TABS = "recent_tabs";
- public static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
- public static final String LIST_TAG_REMOTE_TABS = "remote_tabs";
-
- public interface OnUrlOpenListener {
- public enum Flags {
- ALLOW_SWITCH_TO_TAB,
- OPEN_WITH_INTENT,
- /**
- * Ensure that the raw URL is opened. If not set, then the reader view version of the page
- * might be opened if the URL is stored as an offline reader-view bookmark.
- */
- NO_READER_VIEW
- }
-
- public void onUrlOpen(String url, EnumSet<Flags> flags);
- }
-
- /**
- * Interface for requesting a new tab be opened in the background.
- * <p>
- * This is the <code>HomeFragment</code> equivalent of opening a new tab by
- * long clicking a link and selecting the "Open new [private] tab" context
- * menu option.
- */
- public interface OnUrlOpenInBackgroundListener {
- public enum Flags {
- PRIVATE,
- }
-
- /**
- * Open a new tab with the given URL
- *
- * @param url to open.
- * @param flags to open new tab with.
- */
- public void onUrlOpenInBackground(String url, EnumSet<Flags> flags);
- }
-
- /**
- * Special type of child views that could be added as pager decorations by default.
- */
- public interface Decor {
- void onAddPagerView(String title);
- void removeAllPagerViews();
- void onPageSelected(int position);
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
- void setOnTitleClickListener(TabMenuStrip.OnTitleClickListener onTitleClickListener);
- }
-
- /**
- * State of HomePager with respect to loading its configuration.
- */
- private enum LoadState {
- UNLOADED,
- LOADING,
- LOADED
- }
-
- public static final String CAN_LOAD_ARG = "canLoad";
- public static final String PANEL_CONFIG_ARG = "panelConfig";
-
- public HomePager(Context context) {
- this(context, null);
- }
-
- public HomePager(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- mConfig = HomeConfig.getDefault(mContext);
- mConfigLoaderCallbacks = new ConfigLoaderCallbacks();
-
- mAddPanelListener = new OnAddPanelListener() {
- @Override
- public void onAddPanel(String title) {
- if (mDecor != null) {
- mDecor.onAddPagerView(title);
- }
- }
- };
-
- // This is to keep all 4 panels in memory after they are
- // selected in the pager.
- setOffscreenPageLimit(3);
-
- // We can call HomePager.requestFocus to steal focus from the URL bar and drop the soft
- // keyboard. However, if there are no focusable views (e.g. an empty reading list), the
- // URL bar will be refocused. Therefore, we make the HomePager container focusable to
- // ensure there is always a focusable view. This would ordinarily be done via an XML
- // attribute, but it is not working properly.
- setFocusableInTouchMode(true);
-
- mOriginalBackground = getBackground();
- setOnPageChangeListener(new PageChangeListener());
-
- mLoadState = LoadState.UNLOADED;
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (child instanceof Decor) {
- ((ViewPager.LayoutParams) params).isDecor = true;
- mDecor = (Decor) child;
- mTabStrip = child;
-
- mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
- @Override
- public void onTitleClicked(int index) {
- setCurrentItem(index, true);
- }
- });
- }
-
- super.addView(child, index, params);
- }
-
- /**
- * Loads and initializes the pager.
- *
- * @param fm FragmentManager for the adapter
- */
- @Override
- public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData, PropertyAnimator animator) {
- mLoadState = LoadState.LOADING;
-
- mVisible = true;
- mInitialPanelId = panelId;
- mRestoreData = restoreData;
-
- // Update the home banner message each time the HomePager is loaded.
- if (mHomeBanner != null) {
- mHomeBanner.update();
- }
-
- // Only animate on post-HC devices, when a non-null animator is given
- final boolean shouldAnimate = animator != null;
-
- final HomeAdapter adapter = new HomeAdapter(mContext, fm);
- adapter.setOnAddPanelListener(mAddPanelListener);
- adapter.setPanelStateChangeListener(mPanelStateChangeListener);
- adapter.setCanLoadHint(true);
- setAdapter(adapter);
-
- // Don't show the tabs strip until we have the
- // list of panels in place.
- mTabStrip.setVisibility(View.INVISIBLE);
-
- // Load list of panels from configuration
- lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
-
- if (shouldAnimate) {
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
-
- ViewHelper.setAlpha(this, 0.0f);
-
- animator.attach(this,
- PropertyAnimator.Property.ALPHA,
- 1.0f);
- }
- }
-
- /**
- * Removes all child fragments to free memory.
- */
- @Override
- public void unload() {
- mVisible = false;
- setAdapter(null);
- mLoadState = LoadState.UNLOADED;
-
- // Stop UI Telemetry sessions.
- stopCurrentPanelTelemetrySession();
- }
-
- /**
- * Determines whether the pager is visible.
- *
- * Unlike getVisibility(), this method does not need to be called on the UI
- * thread.
- *
- * @return Whether the pager and its fragments are loaded
- */
- public boolean isVisible() {
- return mVisible;
- }
-
- @Override
- public void setCurrentItem(int item, boolean smoothScroll) {
- super.setCurrentItem(item, smoothScroll);
-
- if (mDecor != null) {
- mDecor.onPageSelected(item);
- }
-
- if (mHomeBanner != null) {
- mHomeBanner.setActive(item == mDefaultPageIndex);
- }
- }
-
- private void restorePanelData(int item, Bundle data) {
- ((HomeAdapter) getAdapter()).setRestoreData(item, data);
- }
-
- /**
- * Shows a home panel. If the given panelId is null,
- * the default panel will be shown. No action will be taken if:
- * * HomePager has not loaded yet
- * * Panel with the given panelId cannot be found
- *
- * If you're trying to open a built-in panel, consider loading the panel url directly with
- * {@link org.mozilla.gecko.AboutPages#getURLForBuiltinPanelType(HomeConfig.PanelType)}.
- *
- * @param panelId of the home panel to be shown.
- */
- @Override
- public void showPanel(String panelId, Bundle restoreData) {
- if (!mVisible) {
- return;
- }
-
- switch (mLoadState) {
- case LOADING:
- mInitialPanelId = panelId;
- mRestoreData = restoreData;
- break;
-
- case LOADED:
- int position = mDefaultPageIndex;
- if (panelId != null) {
- position = ((HomeAdapter) getAdapter()).getItemPosition(panelId);
- }
-
- if (position > -1) {
- setCurrentItem(position);
- if (restoreData != null) {
- restorePanelData(position, restoreData);
- }
- }
- break;
-
- default:
- // Do nothing.
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Drop the soft keyboard by stealing focus from the URL bar.
- requestFocus();
- }
-
- return super.onInterceptTouchEvent(event);
- }
-
- public void setBanner(HomeBanner banner) {
- mHomeBanner = banner;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mHomeBanner != null) {
- mHomeBanner.handleHomeTouch(event);
- }
-
- return super.dispatchTouchEvent(event);
- }
-
- @Override
- public void onToolbarFocusChange(boolean hasFocus) {
- if (mHomeBanner == null) {
- return;
- }
-
- // We should only make the banner active if the toolbar is not focused and we are on the default page
- final boolean active = !hasFocus && getCurrentItem() == mDefaultPageIndex;
- mHomeBanner.setActive(active);
- }
-
- private void updateUiFromConfigState(HomeConfig.State configState) {
- // We only care about the adapter if HomePager is currently
- // loaded, which means it's visible in the activity.
- if (!mVisible) {
- return;
- }
-
- if (mDecor != null) {
- mDecor.removeAllPagerViews();
- }
-
- final HomeAdapter adapter = (HomeAdapter) getAdapter();
-
- // Disable any fragment loading until we have the initial
- // panel selection done.
- adapter.setCanLoadHint(false);
-
- // Destroy any existing panels currently loaded
- // in the pager.
- setAdapter(null);
-
- // Only keep enabled panels.
- final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
-
- for (PanelConfig panelConfig : configState) {
- if (!panelConfig.isDisabled()) {
- enabledPanels.add(panelConfig);
- }
- }
-
- // Update the adapter with the new panel configs
- adapter.update(enabledPanels);
-
- final int count = enabledPanels.size();
- if (count == 0) {
- // Set firefox watermark as background.
- setBackgroundResource(R.drawable.home_pager_empty_state);
- // Hide the tab strip as there are no panels.
- mTabStrip.setVisibility(View.INVISIBLE);
- } else {
- mTabStrip.setVisibility(View.VISIBLE);
- // Restore original background.
- setBackgroundDrawable(mOriginalBackground);
- }
-
- // Re-install the adapter with the final state
- // in the pager.
- setAdapter(adapter);
-
- if (count == 0) {
- mDefaultPageIndex = -1;
-
- // Hide the banner if there are no enabled panels.
- if (mHomeBanner != null) {
- mHomeBanner.setActive(false);
- }
- } else {
- for (int i = 0; i < count; i++) {
- if (enabledPanels.get(i).isDefault()) {
- mDefaultPageIndex = i;
- break;
- }
- }
-
- // Use the default panel if the initial panel wasn't explicitly set by the
- // load() caller, or if the initial panel is not found in the adapter.
- final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
- if (itemPosition > -1) {
- setCurrentItem(itemPosition, false);
- if (mRestoreData != null) {
- restorePanelData(itemPosition, mRestoreData);
- mRestoreData = null; // Release data since it's no longer needed
- }
- mInitialPanelId = null;
- } else {
- setCurrentItem(mDefaultPageIndex, false);
- }
- }
-
- // The selection is updated asynchronously so we need to post to
- // UI thread to give the pager time to commit the new page selection
- // internally and load the right initial panel.
- ThreadUtils.getUiHandler().post(new Runnable() {
- @Override
- public void run() {
- adapter.setCanLoadHint(true);
- }
- });
- }
-
- @Override
- public void setOnPanelChangeListener(OnPanelChangeListener listener) {
- mPanelChangedListener = listener;
- }
-
- @Override
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
- mPanelStateChangeListener = listener;
-
- HomeAdapter adapter = (HomeAdapter) getAdapter();
- if (adapter != null) {
- adapter.setPanelStateChangeListener(listener);
- }
- }
-
- /**
- * Notify listeners of newly selected panel.
- *
- * @param position of the newly selected panel
- */
- private void notifyPanelSelected(int position) {
- if (mDecor != null) {
- mDecor.onPageSelected(position);
- }
-
- if (mPanelChangedListener != null) {
- final String panelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
- mPanelChangedListener.onPanelSelected(panelId);
- }
- }
-
- private class ConfigLoaderCallbacks implements LoaderCallbacks<HomeConfig.State> {
- @Override
- public Loader<HomeConfig.State> onCreateLoader(int id, Bundle args) {
- return new HomeConfigLoader(mContext, mConfig);
- }
-
- @Override
- public void onLoadFinished(Loader<HomeConfig.State> loader, HomeConfig.State configState) {
- mLoadState = LoadState.LOADED;
- updateUiFromConfigState(configState);
- }
-
- @Override
- public void onLoaderReset(Loader<HomeConfig.State> loader) {
- mLoadState = LoadState.UNLOADED;
- }
- }
-
- private class PageChangeListener implements ViewPager.OnPageChangeListener {
- @Override
- public void onPageSelected(int position) {
- notifyPanelSelected(position);
-
- if (mHomeBanner != null) {
- mHomeBanner.setActive(position == mDefaultPageIndex);
- }
-
- // Start a UI telemetry session for the newly selected panel.
- final String newPanelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
- startNewPanelTelemetrySession(newPanelId);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- if (mDecor != null) {
- mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- if (mHomeBanner != null) {
- mHomeBanner.setScrollingPages(positionOffsetPixels != 0);
- }
- }
-
- @Override
- public void onPageScrollStateChanged(int state) { }
- }
-
- /**
- * Start UI telemetry session for the a panel.
- * If there is currently a session open for a panel,
- * it will be stopped before a new one is started.
- *
- * @param panelId of panel to start a session for
- */
- private void startNewPanelTelemetrySession(String panelId) {
- // Stop the current panel's session if we have one.
- stopCurrentPanelTelemetrySession();
-
- mCurrentPanelSession = TelemetryContract.Session.HOME_PANEL;
- mCurrentPanelSessionSuffix = panelId;
- Telemetry.startUISession(mCurrentPanelSession, mCurrentPanelSessionSuffix);
- }
-
- /**
- * Stop the current panel telemetry session if one exists.
- */
- private void stopCurrentPanelTelemetrySession() {
- if (mCurrentPanelSession != null) {
- Telemetry.stopUISession(mCurrentPanelSession, mCurrentPanelSessionSuffix);
- mCurrentPanelSession = null;
- mCurrentPanelSessionSuffix = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java b/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
deleted file mode 100644
index bfd6c5624..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.db.HomeProvider;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
-import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-public class HomePanelsManager implements GeckoEventListener {
- public static final String LOGTAG = "HomePanelsManager";
-
- private static final HomePanelsManager sInstance = new HomePanelsManager();
-
- private static final int INVALIDATION_DELAY_MSEC = 500;
- private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
-
- private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
- private static final String EVENT_HOMEPANELS_UNINSTALL = "HomePanels:Uninstall";
- private static final String EVENT_HOMEPANELS_UPDATE = "HomePanels:Update";
- private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:RefreshDataset";
-
- private static final String JSON_KEY_PANEL = "panel";
- private static final String JSON_KEY_PANEL_ID = "id";
-
- private enum ChangeType {
- UNINSTALL,
- INSTALL,
- UPDATE,
- REFRESH
- }
-
- private enum InvalidationMode {
- DELAYED,
- IMMEDIATE
- }
-
- private static class ConfigChange {
- private final ChangeType type;
- private final Object target;
-
- public ConfigChange(ChangeType type) {
- this(type, null);
- }
-
- public ConfigChange(ChangeType type, Object target) {
- this.type = type;
- this.target = target;
- }
- }
-
- private Context mContext;
- private HomeConfig mHomeConfig;
- private boolean mInitialized;
-
- private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
- private final Runnable mInvalidationRunnable = new InvalidationRunnable();
-
- public static HomePanelsManager getInstance() {
- return sInstance;
- }
-
- public void init(Context context) {
- if (mInitialized) {
- return;
- }
-
- mContext = context;
- mHomeConfig = HomeConfig.getDefault(context);
-
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- EVENT_HOMEPANELS_INSTALL,
- EVENT_HOMEPANELS_UNINSTALL,
- EVENT_HOMEPANELS_UPDATE,
- EVENT_HOMEPANELS_REFRESH);
-
- mInitialized = true;
- }
-
- public void onLocaleReady(final String locale) {
- ThreadUtils.getBackgroundHandler().post(new Runnable() {
- @Override
- public void run() {
- final String configLocale = mHomeConfig.getLocale();
- if (configLocale == null || !configLocale.equals(locale)) {
- handleLocaleChange();
- }
- }
- });
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
- handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
- } else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
- final String panelId = message.getString(JSON_KEY_PANEL_ID);
- handlePanelUninstall(panelId);
- } else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
- handlePanelUpdate(createPanelConfigFromMessage(message));
- } else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
- handleDatasetRefresh(message);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to handle event " + event, e);
- }
- }
-
- private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
- final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
- return new PanelConfig(json);
- }
-
- /**
- * Adds a new PanelConfig to the HomeConfig.
- *
- * This posts the invalidation of HomeConfig immediately.
- *
- * @param panelConfig panel to add
- */
- public void installPanel(PanelConfig panelConfig) {
- Log.d(LOGTAG, "installPanel: " + panelConfig.getTitle());
- handlePanelInstall(panelConfig, InvalidationMode.IMMEDIATE);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelInstall(PanelConfig panelConfig, InvalidationMode mode) {
- mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
- Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
-
- scheduleInvalidation(mode);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelUninstall(String panelId) {
- mPendingChanges.offer(new ConfigChange(ChangeType.UNINSTALL, panelId));
- Log.d(LOGTAG, "handlePanelUninstall: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.DELAYED);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelUpdate(PanelConfig panelConfig) {
- mPendingChanges.offer(new ConfigChange(ChangeType.UPDATE, panelConfig));
- Log.d(LOGTAG, "handlePanelUpdate: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.DELAYED);
- }
-
- /**
- * Runs in the background thread.
- */
- private void handleLocaleChange() {
- mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH));
- Log.d(LOGTAG, "handleLocaleChange: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.IMMEDIATE);
- }
-
-
- /**
- * Handles a dataset refresh request from Gecko. This is usually
- * triggered by a HomeStorage.save() call in an add-on.
- *
- * Runs in the gecko thread.
- */
- private void handleDatasetRefresh(JSONObject message) {
- final String datasetId;
- try {
- datasetId = message.getString("datasetId");
- } catch (JSONException e) {
- Log.e(LOGTAG, "Failed to handle dataset refresh", e);
- return;
- }
-
- Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
-
- final ContentResolver cr = mContext.getContentResolver();
- cr.notifyChange(HomeProvider.getDatasetNotificationUri(datasetId), null);
- }
-
- /**
- * Runs in the gecko or main thread.
- */
- private void scheduleInvalidation(InvalidationMode mode) {
- final Handler handler = ThreadUtils.getBackgroundHandler();
-
- handler.removeCallbacks(mInvalidationRunnable);
-
- if (mode == InvalidationMode.IMMEDIATE) {
- handler.post(mInvalidationRunnable);
- } else {
- handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
- }
-
- Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation: " + mode);
- }
-
- /**
- * Runs in the background thread.
- */
- private void executePendingChanges(HomeConfig.Editor editor) {
- boolean shouldRefresh = false;
-
- while (!mPendingChanges.isEmpty()) {
- final ConfigChange pendingChange = mPendingChanges.poll();
-
- switch (pendingChange.type) {
- case UNINSTALL: {
- final String panelId = (String) pendingChange.target;
- if (editor.uninstall(panelId)) {
- Log.d(LOGTAG, "executePendingChanges: uninstalled panel " + panelId);
- }
- break;
- }
-
- case INSTALL: {
- final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
- if (editor.install(panelConfig)) {
- Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId());
- }
- break;
- }
-
- case UPDATE: {
- final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
- if (editor.update(panelConfig)) {
- Log.w(LOGTAG, "executePendingChanges: updated panel " + panelConfig.getId());
- }
- break;
- }
-
- case REFRESH: {
- shouldRefresh = true;
- }
- }
- }
-
- // The editor still represents the default HomeConfig
- // configuration and hasn't been changed by any operation
- // above. No need to refresh as the HomeConfig backend will
- // take of forcing all existing HomeConfigLoader instances to
- // refresh their contents.
- if (shouldRefresh && !editor.isDefault()) {
- executeRefresh(editor);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private void refreshFromPanelInfos(HomeConfig.Editor editor, List<PanelInfo> panelInfos) {
- Log.d(LOGTAG, "refreshFromPanelInfos");
-
- for (PanelConfig panelConfig : editor) {
- PanelConfig refreshedPanelConfig = null;
-
- if (panelConfig.isDynamic()) {
- for (PanelInfo panelInfo : panelInfos) {
- if (panelInfo.getId().equals(panelConfig.getId())) {
- refreshedPanelConfig = panelInfo.toPanelConfig();
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
- break;
- }
- }
- } else {
- refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
- }
-
- if (refreshedPanelConfig == null) {
- Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
- continue;
- }
-
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshed panel " + refreshedPanelConfig.getId());
- editor.update(refreshedPanelConfig);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private void executeRefresh(HomeConfig.Editor editor) {
- if (editor.isEmpty()) {
- return;
- }
-
- Log.d(LOGTAG, "executeRefresh");
-
- final Set<String> ids = new HashSet<String>();
- for (PanelConfig panelConfig : editor) {
- ids.add(panelConfig.getId());
- }
-
- final Object panelRequestLock = new Object();
- final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
-
- final PanelInfoManager pm = new PanelInfoManager();
- pm.requestPanelsById(ids, new RequestCallback() {
- @Override
- public void onComplete(List<PanelInfo> panelInfos) {
- synchronized (panelRequestLock) {
- latestPanelInfos.addAll(panelInfos);
- Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
-
- panelRequestLock.notifyAll();
- }
- }
- });
-
- try {
- synchronized (panelRequestLock) {
- panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
-
- Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
- refreshFromPanelInfos(editor, latestPanelInfos);
- }
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private class InvalidationRunnable implements Runnable {
- @Override
- public void run() {
- final HomeConfig.Editor editor = mHomeConfig.load().edit();
- executePendingChanges(editor);
- editor.apply();
- }
- };
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java
deleted file mode 100644
index 1525969a0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.view.View;
-
-import org.mozilla.gecko.animation.PropertyAnimator;
-
-/**
- * Generic interface for any View that can be used as the homescreen.
- *
- * In the past we had the HomePager, which contained the usual homepanels (multiple panels: TopSites,
- * bookmarks, history, etc.), which could be swiped between.
- *
- * This interface allows easily switching between different homepanel implementations. For example
- * the prototype activity-stream panel (which will be a single panel combining the functionality
- * of the previous panels).
- */
-public interface HomeScreen {
- /**
- * Interface for listening into ViewPager panel changes
- */
- public interface OnPanelChangeListener {
- /**
- * Called when a new panel is selected.
- *
- * @param panelId of the newly selected panel
- */
- public void onPanelSelected(String panelId);
- }
-
- // The following two methods are actually methods of View. Since there is no View interface
- // we're forced to do this instead of "extending" View. Any class implementing HomeScreen
- // will have to implement these and pass them through to the underlying View.
- boolean isVisible();
- boolean requestFocus();
-
- void onToolbarFocusChange(boolean hasFocus);
-
- // The following three methods are HomePager specific. The persistence framework might need
- // refactoring/generalising at some point, but it isn't entirely clear what other panels
- // might need so we can leave these as is for now.
- void showPanel(String panelId, Bundle restoreData);
- void setOnPanelChangeListener(OnPanelChangeListener listener);
- void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener);
-
- /**
- * Set a banner that may be displayed at the bottom of the HomeScreen. This can be used
- * e.g. to show snippets.
- */
- void setBanner(HomeBanner banner);
-
- void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData, PropertyAnimator animator);
-
- void unload();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java
deleted file mode 100644
index 2bbd82a8d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.DisplayMetrics;
-import android.util.Log;
-
-import com.squareup.picasso.LruCache;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Downloader.Response;
-import com.squareup.picasso.UrlConnectionDownloader;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-import org.mozilla.gecko.distribution.Distribution;
-
-public class ImageLoader {
- private static final String LOGTAG = "GeckoImageLoader";
-
- private static final String DISTRIBUTION_SCHEME = "gecko.distribution";
- private static final String SUGGESTED_SITES_AUTHORITY = "suggestedsites";
-
- // The order of density factors to try when looking for an image resource
- // in the distribution directory. It looks for an exact match first (1.0) then
- // tries to find images with higher density (2.0 and 1.5). If no image is found,
- // try a lower density (0.5). See loadDistributionImage().
- private static final float[] densityFactors = new float[] { 1.0f, 2.0f, 1.5f, 0.5f };
-
- private static enum Density {
- MDPI,
- HDPI,
- XHDPI,
- XXHDPI;
-
- @Override
- public String toString() {
- return super.toString().toLowerCase();
- }
- }
-
- // Picasso instance and LruCache lrucache are protected by synchronization.
- private static Picasso instance;
- private static LruCache lrucache;
-
- public static synchronized Picasso with(Context context) {
- if (instance == null) {
- lrucache = new LruCache(context);
- Picasso.Builder builder = new Picasso.Builder(context).memoryCache(lrucache);
-
- final Distribution distribution = Distribution.getInstance(context.getApplicationContext());
- builder.downloader(new ImageDownloader(context, distribution));
- instance = builder.build();
- }
-
- return instance;
- }
-
- public static synchronized void clearLruCache() {
- if (lrucache != null) {
- lrucache.evictAll();
- }
- }
-
- /**
- * Custom Downloader built on top of Picasso's UrlConnectionDownloader
- * that supports loading images from custom URIs.
- */
- public static class ImageDownloader extends UrlConnectionDownloader {
- private final Context context;
- private final Distribution distribution;
-
- public ImageDownloader(Context context, Distribution distribution) {
- super(context);
- this.context = context;
- this.distribution = distribution;
- }
-
- private Density getDensity(float factor) {
- final DisplayMetrics dm = context.getResources().getDisplayMetrics();
- final float densityDpi = dm.densityDpi * factor;
-
- if (densityDpi >= DisplayMetrics.DENSITY_XXHIGH) {
- return Density.XXHDPI;
- } else if (densityDpi >= DisplayMetrics.DENSITY_XHIGH) {
- return Density.XHDPI;
- } else if (densityDpi >= DisplayMetrics.DENSITY_HIGH) {
- return Density.HDPI;
- }
-
- // Fallback to mdpi, no need to handle ldpi.
- return Density.MDPI;
- }
-
- @Override
- public Response load(Uri uri, boolean localCacheOnly) throws IOException {
- final String scheme = uri.getScheme();
- if (DISTRIBUTION_SCHEME.equals(scheme)) {
- return loadDistributionImage(uri);
- }
-
- return super.load(uri, localCacheOnly);
- }
-
- private static String getPathForDensity(String basePath, Density density,
- String filename) {
- final File dir = new File(basePath, density.toString());
- return String.format("%s/%s.png", dir.toString(), filename);
- }
-
- /**
- * Handle distribution URIs in Picasso. The expected format is:
- *
- * gecko.distribution://<basepath>/<imagename>
- *
- * Which will look for the following file in the distribution:
- *
- * <distribution-root-dir>/<basepath>/<device-density>/<imagename>.png
- */
- private Response loadDistributionImage(Uri uri) throws IOException {
- // Eliminate the leading '//'
- final String ssp = uri.getSchemeSpecificPart().substring(2);
-
- final String filename;
- final String basePath;
-
- final int slashIndex = ssp.lastIndexOf('/');
- if (slashIndex == -1) {
- filename = ssp;
- basePath = "";
- } else {
- filename = ssp.substring(slashIndex + 1);
- basePath = ssp.substring(0, slashIndex);
- }
-
- Set<Density> triedDensities = EnumSet.noneOf(Density.class);
-
- for (int i = 0; i < densityFactors.length; i++) {
- final Density density = getDensity(densityFactors[i]);
- if (!triedDensities.add(density)) {
- continue;
- }
-
- final String path = getPathForDensity(basePath, density, filename);
- Log.d(LOGTAG, "Trying to load image from distribution " + path);
-
- final File f = distribution.getDistributionFile(path);
- if (f != null) {
- return new Response(new FileInputStream(f), true);
- }
- }
-
- throw new ResponseException("Couldn't find suggested site image in distribution");
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java
deleted file mode 100644
index 26edf13ff..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.CursorAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * MultiTypeCursorAdapter wraps a cursor and any meta data associated with it.
- * A set of view types (corresponding to the cursor and its meta data)
- * are mapped to a set of layouts.
- */
-abstract class MultiTypeCursorAdapter extends CursorAdapter {
- private final int[] mViewTypes;
- private final int[] mLayouts;
-
- // Bind the view for the given position.
- abstract public void bindView(View view, Context context, int position);
-
- public MultiTypeCursorAdapter(Context context, Cursor cursor, int[] viewTypes, int[] layouts) {
- super(context, cursor, 0);
-
- if (viewTypes.length != layouts.length) {
- throw new IllegalStateException("The view types and the layouts should be of same size");
- }
-
- mViewTypes = viewTypes;
- mLayouts = layouts;
- }
-
- @Override
- public final int getViewTypeCount() {
- return mViewTypes.length;
- }
-
- /**
- * @return Cursor for the given position.
- */
- public final Cursor getCursor(int position) {
- final Cursor cursor = getCursor();
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- return cursor;
- }
-
- @Override
- public final View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- if (convertView == null) {
- convertView = newView(context, position, parent);
- }
-
- bindView(convertView, context, position);
- return convertView;
- }
-
- @Override
- public final void bindView(View view, Context context, Cursor cursor) {
- // Do nothing.
- }
-
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- @Override
- public final View newView(Context context, Cursor cursor, ViewGroup parent) {
- return null;
- }
-
- /**
- * Inflate a new view from a set of view types and layouts based on the position.
- *
- * @param context Context for inflating the view.
- * @param position Position of the view.
- * @param parent Parent view group that will hold this view.
- */
- private View newView(Context context, int position, ViewGroup parent) {
- final int type = getItemViewType(position);
- final int count = mViewTypes.length;
-
- for (int i = 0; i < count; i++) {
- if (mViewTypes[i] == type) {
- return LayoutInflater.from(context).inflate(mLayouts[i], parent, false);
- }
- }
-
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java
deleted file mode 100644
index d66919344..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-
-/**
- * Cache used to store authentication state of dynamic panels. The values
- * in this cache are set in JS through the Home.panels API.
- *
- * {@code DynamicPanel} uses this cache to determine whether or not to
- * show authentication UI for dynamic panels, including listening for
- * changes in authentication state.
- */
-class PanelAuthCache {
- private static final String LOGTAG = "GeckoPanelAuthCache";
-
- // Keep this in sync with the constant defined in Home.jsm
- private static final String PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
-
- private final Context mContext;
- private SharedPrefsListener mSharedPrefsListener;
- private OnChangeListener mChangeListener;
-
- public interface OnChangeListener {
- public void onChange(String panelId, boolean isAuthenticated);
- }
-
- public PanelAuthCache(Context context) {
- mContext = context;
- }
-
- private SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forProfile(mContext);
- }
-
- private String getPanelAuthKey(String panelId) {
- return PREFS_PANEL_AUTH_PREFIX + panelId;
- }
-
- public boolean isAuthenticated(String panelId) {
- final SharedPreferences prefs = getSharedPreferences();
- return prefs.getBoolean(getPanelAuthKey(panelId), false);
- }
-
- public void setOnChangeListener(OnChangeListener listener) {
- final SharedPreferences prefs = getSharedPreferences();
-
- if (mChangeListener != null) {
- prefs.unregisterOnSharedPreferenceChangeListener(mSharedPrefsListener);
- mSharedPrefsListener = null;
- }
-
- mChangeListener = listener;
-
- if (mChangeListener != null) {
- mSharedPrefsListener = new SharedPrefsListener();
- prefs.registerOnSharedPreferenceChangeListener(mSharedPrefsListener);
- }
- }
-
- private class SharedPrefsListener implements OnSharedPreferenceChangeListener {
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.startsWith(PREFS_PANEL_AUTH_PREFIX)) {
- final String panelId = key.substring(PREFS_PANEL_AUTH_PREFIX.length());
- final boolean isAuthenticated = prefs.getBoolean(key, false);
-
- Log.d(LOGTAG, "Auth state changed: panelId=" + panelId + ", isAuthenticated=" + isAuthenticated);
- mChangeListener.onChange(panelId, isAuthenticated);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
deleted file mode 100644
index 1ad91b7ca..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomeConfig.AuthConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.squareup.picasso.Picasso;
-
-class PanelAuthLayout extends LinearLayout {
-
- public PanelAuthLayout(Context context, PanelConfig panelConfig) {
- super(context);
-
- final AuthConfig authConfig = panelConfig.getAuthConfig();
- if (authConfig == null) {
- throw new IllegalStateException("Can't create PanelAuthLayout without a valid AuthConfig");
- }
-
- setOrientation(LinearLayout.VERTICAL);
- LayoutInflater.from(context).inflate(R.layout.panel_auth_layout, this);
-
- final TextView messageView = (TextView) findViewById(R.id.message);
- messageView.setText(authConfig.getMessageText());
-
- final Button buttonView = (Button) findViewById(R.id.button);
- buttonView.setText(authConfig.getButtonText());
-
- final String panelId = panelConfig.getId();
- buttonView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- GeckoAppShell.notifyObservers("HomePanels:Authenticate", panelId);
- }
- });
-
- final ImageView imageView = (ImageView) findViewById(R.id.image);
- final String imageUrl = authConfig.getImageUrl();
-
- if (TextUtils.isEmpty(imageUrl)) {
- // Use a default image if an image URL isn't specified.
- imageView.setImageResource(R.drawable.icon_home_empty_firefox);
- } else {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .into(imageView);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java
deleted file mode 100644
index 4772e08ab..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.PanelLayout.FilterDetail;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.squareup.picasso.Picasso;
-
-class PanelBackItemView extends LinearLayout {
- private final TextView title;
-
- public PanelBackItemView(Context context, String backImageUrl) {
- super(context);
-
- LayoutInflater.from(context).inflate(R.layout.panel_back_item, this);
- setOrientation(HORIZONTAL);
-
- title = (TextView) findViewById(R.id.title);
-
- final ImageView image = (ImageView) findViewById(R.id.image);
-
- if (TextUtils.isEmpty(backImageUrl)) {
- image.setImageResource(R.drawable.arrow_up);
- } else {
- ImageLoader.with(getContext())
- .load(backImageUrl)
- .placeholder(R.drawable.arrow_up)
- .into(image);
- }
- }
-
- public void updateFromFilter(FilterDetail filter) {
- final String backText = getResources()
- .getString(R.string.home_move_back_to_filter, filter.title);
- title.setText(backText);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java
deleted file mode 100644
index 50c4dbc07..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.widget.ImageView;
-
-@SuppressLint("ViewConstructor") // View is only created from code
-public class PanelHeaderView extends ImageView {
- public PanelHeaderView(Context context, HomeConfig.HeaderConfig config) {
- super(context);
-
- setAdjustViewBounds(true);
-
- ImageLoader.with(context)
- .load(config.getImageUrl())
- .into(this);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
-
- // Always span the whole width and adjust height as needed.
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
deleted file mode 100644
index 089e17837..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-public class PanelInfoManager implements GeckoEventListener {
- private static final String LOGTAG = "GeckoPanelInfoManager";
-
- public class PanelInfo {
- private final String mId;
- private final String mTitle;
- private final JSONObject mJSONData;
-
- public PanelInfo(String id, String title, JSONObject jsonData) {
- mId = id;
- mTitle = title;
- mJSONData = jsonData;
- }
-
- public String getId() {
- return mId;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public PanelConfig toPanelConfig() {
- try {
- return new PanelConfig(mJSONData);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to convert PanelInfo to PanelConfig", e);
- return null;
- }
- }
- }
-
- public interface RequestCallback {
- public void onComplete(List<PanelInfo> panelInfos);
- }
-
- private static final AtomicInteger sRequestId = new AtomicInteger(0);
-
- // Stores set of pending request callbacks.
- private static final SparseArray<RequestCallback> sCallbacks = new SparseArray<RequestCallback>();
-
- /**
- * Asynchronously fetches list of available panels from Gecko
- * for the given IDs.
- *
- * @param ids list of panel ids to be fetched. A null value will fetch all
- * available panels.
- * @param callback onComplete will be called on the UI thread.
- */
- public void requestPanelsById(Set<String> ids, RequestCallback callback) {
- final int requestId = sRequestId.getAndIncrement();
-
- synchronized (sCallbacks) {
- // If there are no pending callbacks, register the event listener.
- if (sCallbacks.size() == 0) {
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "HomePanels:Data");
- }
- sCallbacks.put(requestId, callback);
- }
-
- final JSONObject message = new JSONObject();
- try {
- message.put("requestId", requestId);
-
- if (ids != null && ids.size() > 0) {
- JSONArray idsArray = new JSONArray();
- for (String id : ids) {
- idsArray.put(id);
- }
-
- message.put("ids", idsArray);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Failed to build event to request panels by id", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("HomePanels:Get", message.toString());
- }
-
- /**
- * Asynchronously fetches list of available panels from Gecko.
- *
- * @param callback onComplete will be called on the UI thread.
- */
- public void requestAvailablePanels(RequestCallback callback) {
- requestPanelsById(null, callback);
- }
-
- /**
- * Handles "HomePanels:Data" events.
- */
- @Override
- public void handleMessage(String event, JSONObject message) {
- final ArrayList<PanelInfo> panelInfos = new ArrayList<PanelInfo>();
-
- try {
- final JSONArray panels = message.getJSONArray("panels");
- final int count = panels.length();
- for (int i = 0; i < count; i++) {
- final PanelInfo panelInfo = getPanelInfoFromJSON(panels.getJSONObject(i));
- panelInfos.add(panelInfo);
- }
-
- final RequestCallback callback;
- final int requestId = message.getInt("requestId");
-
- synchronized (sCallbacks) {
- callback = sCallbacks.get(requestId);
- sCallbacks.delete(requestId);
-
- // Unregister the event listener if there are no more pending callbacks.
- if (sCallbacks.size() == 0) {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "HomePanels:Data");
- }
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- callback.onComplete(panelInfos);
- }
- });
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception handling " + event + " message", e);
- }
- }
-
- private PanelInfo getPanelInfoFromJSON(JSONObject jsonPanelInfo) throws JSONException {
- final String id = jsonPanelInfo.getString("id");
- final String title = jsonPanelInfo.getString("title");
-
- return new PanelInfo(id, title, jsonPanelInfo);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java
deleted file mode 100644
index 2a97d42bc..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ItemType;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-class PanelItemView extends LinearLayout {
- private final TextView titleView;
- private final TextView descriptionView;
- private final ImageView imageView;
- private final LinearLayout titleDescContainerView;
- private final ImageView backgroundView;
-
- private PanelItemView(Context context, int layoutId) {
- super(context);
-
- LayoutInflater.from(context).inflate(layoutId, this);
- titleView = (TextView) findViewById(R.id.title);
- descriptionView = (TextView) findViewById(R.id.description);
- imageView = (ImageView) findViewById(R.id.image);
- backgroundView = (ImageView) findViewById(R.id.background);
- titleDescContainerView = (LinearLayout) findViewById(R.id.title_desc_container);
- }
-
- public void updateFromCursor(Cursor cursor) {
- int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
- final String titleText = cursor.getString(titleIndex);
-
- // Only show title if the item has one
- final boolean hasTitle = !TextUtils.isEmpty(titleText);
- titleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
- if (hasTitle) {
- titleView.setText(titleText);
- }
-
- int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
- final String descriptionText = cursor.getString(descriptionIndex);
-
- // Only show description if the item has one
- // Descriptions are not supported for IconItemView objects (Bug 1157539)
- final boolean hasDescription = !TextUtils.isEmpty(descriptionText);
- if (descriptionView != null) {
- descriptionView.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
- if (hasDescription) {
- descriptionView.setText(descriptionText);
- }
- }
- if (titleDescContainerView != null) {
- titleDescContainerView.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
- }
-
- int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
- final String imageUrl = cursor.getString(imageIndex);
-
- // Only try to load the image if the item has define image URL
- final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
- imageView.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
-
- if (hasImageUrl) {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .into(imageView);
- }
-
- final int columnIndexBackgroundColor = cursor.getColumnIndex(HomeItems.BACKGROUND_COLOR);
- if (columnIndexBackgroundColor != -1) {
- final String color = cursor.getString(columnIndexBackgroundColor);
- if (!TextUtils.isEmpty(color)) {
- setBackgroundColor(Color.parseColor(color));
- }
- }
-
- // Backgrounds are only supported for IconItemView objects (Bug 1157539)
- final int columnIndexBackgroundUrl = cursor.getColumnIndex(HomeItems.BACKGROUND_URL);
- if (columnIndexBackgroundUrl != -1) {
- final String backgroundUrl = cursor.getString(columnIndexBackgroundUrl);
- if (backgroundView != null && !TextUtils.isEmpty(backgroundUrl)) {
- ImageLoader.with(getContext())
- .load(backgroundUrl)
- .fit()
- .into(backgroundView);
- }
- }
- }
-
- private static class ArticleItemView extends PanelItemView {
- private ArticleItemView(Context context) {
- super(context, R.layout.panel_article_item);
- setOrientation(LinearLayout.HORIZONTAL);
- }
- }
-
- private static class ImageItemView extends PanelItemView {
- private ImageItemView(Context context) {
- super(context, R.layout.panel_image_item);
- setOrientation(LinearLayout.VERTICAL);
- }
- }
-
- private static class IconItemView extends PanelItemView {
- private IconItemView(Context context) {
- super(context, R.layout.panel_icon_item);
- }
- }
-
- public static PanelItemView create(Context context, ItemType itemType) {
- switch (itemType) {
- case ARTICLE:
- return new ArticleItemView(context);
-
- case IMAGE:
- return new ImageItemView(context);
-
- case ICON:
- return new IconItemView(context);
-
- default:
- throw new IllegalArgumentException("Could not create panel item view from " + itemType);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java
deleted file mode 100644
index 2c2d89ae0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java
+++ /dev/null
@@ -1,747 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig;
-import org.mozilla.gecko.home.HomeConfig.ItemHandler;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.lang.ref.SoftReference;
-import java.util.EnumSet;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import com.squareup.picasso.Picasso;
-
-/**
- * {@code PanelLayout} is the base class for custom layouts to be
- * used in {@code DynamicPanel}. It provides the basic framework
- * that enables custom layouts to request and reset datasets and
- * create panel views. Furthermore, it automates most of the process
- * of binding panel views with their respective datasets.
- *
- * {@code PanelLayout} abstracts the implemention details of how
- * datasets are actually loaded through the {@DatasetHandler} interface.
- * {@code DatasetHandler} provides two operations: request and reset.
- * The results of the dataset requests done via the {@code DatasetHandler}
- * are delivered to the {@code PanelLayout} with the {@code deliverDataset()}
- * method.
- *
- * Subclasses of {@code PanelLayout} should simply use the utilities
- * provided by {@code PanelLayout}. Namely:
- *
- * {@code requestDataset()} - To fetch datasets and auto-bind them to
- * the existing panel views backed by them.
- *
- * {@code resetDataset()} - To release any resources associated with a
- * previously loaded dataset.
- *
- * {@code createPanelView()} - To create a panel view for a ViewConfig
- * associated with the panel.
- *
- * {@code disposePanelView()} - To dispose any dataset references associated
- * with the given view.
- *
- * {@code PanelLayout} subclasses should always use {@code createPanelView()}
- * to create the views dynamically created based on {@code ViewConfig}. This
- * allows {@code PanelLayout} to auto-bind datasets with panel views.
- * {@code PanelLayout} subclasses are free to have any type of views to arrange
- * the panel views in different ways.
- */
-abstract class PanelLayout extends FrameLayout {
- private static final String LOGTAG = "GeckoPanelLayout";
-
- protected final SparseArray<ViewState> mViewStates;
- private final PanelConfig mPanelConfig;
- private final DatasetHandler mDatasetHandler;
- private final OnUrlOpenListener mUrlOpenListener;
- private final ContextMenuRegistry mContextMenuRegistry;
-
- /**
- * To be used by panel views to express that they are
- * backed by datasets.
- */
- public interface DatasetBacked {
- public void setDataset(Cursor cursor);
- public void setFilterManager(FilterManager manager);
- }
-
- /**
- * To be used by requests made to {@code DatasetHandler}s to couple dataset ID with current
- * filter for queries on the database.
- */
- public static class DatasetRequest implements Parcelable {
- public enum Type implements Parcelable {
- DATASET_LOAD,
- FILTER_PUSH,
- FILTER_POP;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<Type> CREATOR = new Creator<Type>() {
- @Override
- public Type createFromParcel(final Parcel source) {
- return Type.values()[source.readInt()];
- }
-
- @Override
- public Type[] newArray(final int size) {
- return new Type[size];
- }
- };
- }
-
- private final int mViewIndex;
- private final Type mType;
- private final String mDatasetId;
- private final FilterDetail mFilterDetail;
-
- private DatasetRequest(Parcel in) {
- this.mViewIndex = in.readInt();
- this.mType = (Type) in.readParcelable(getClass().getClassLoader());
- this.mDatasetId = in.readString();
- this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader());
- }
-
- public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) {
- this(index, Type.DATASET_LOAD, datasetId, filterDetail);
- }
-
- public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) {
- this.mViewIndex = index;
- this.mType = type;
- this.mDatasetId = datasetId;
- this.mFilterDetail = filterDetail;
- }
-
- public int getViewIndex() {
- return mViewIndex;
- }
-
- public Type getType() {
- return mType;
- }
-
- public String getDatasetId() {
- return mDatasetId;
- }
-
- public String getFilter() {
- return (mFilterDetail != null ? mFilterDetail.filter : null);
- }
-
- public FilterDetail getFilterDetail() {
- return mFilterDetail;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mViewIndex);
- dest.writeParcelable(mType, 0);
- dest.writeString(mDatasetId);
- dest.writeParcelable(mFilterDetail, 0);
- }
-
- public String toString() {
- return "{ index: " + mViewIndex +
- ", type: " + mType +
- ", dataset: " + mDatasetId +
- ", filter: " + mFilterDetail +
- " }";
- }
-
- public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() {
- @Override
- public DatasetRequest createFromParcel(Parcel in) {
- return new DatasetRequest(in);
- }
-
- @Override
- public DatasetRequest[] newArray(int size) {
- return new DatasetRequest[size];
- }
- };
- }
-
- /**
- * Defines the contract with the component that is responsible
- * for handling datasets requests.
- */
- public interface DatasetHandler {
- /**
- * Requests a dataset to be fetched and auto-bound to the
- * panel views backed by it.
- */
- public void requestDataset(DatasetRequest request);
-
- /**
- * Releases any resources associated with a panel view. It will
- * do nothing if the view with the given index been created
- * before.
- */
- public void resetDataset(int viewIndex);
- }
-
- public interface PanelView {
- public void setOnItemOpenListener(OnItemOpenListener listener);
- public void setOnKeyListener(OnKeyListener listener);
- public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory);
- }
-
- public interface FilterManager {
- public FilterDetail getPreviousFilter();
- public boolean canGoBack();
- public void goBack();
- }
-
- public interface ContextMenuRegistry {
- public void register(View view);
- }
-
- public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
- OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
- super(context);
- mViewStates = new SparseArray<ViewState>();
- mPanelConfig = panelConfig;
- mDatasetHandler = datasetHandler;
- mUrlOpenListener = urlOpenListener;
- mContextMenuRegistry = contextMenuRegistry;
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- final int count = mViewStates.size();
- for (int i = 0; i < count; i++) {
- final ViewState viewState = mViewStates.valueAt(i);
-
- final View view = viewState.getView();
- if (view != null) {
- maybeSetDataset(view, null);
- }
- }
- mViewStates.clear();
- }
-
- /**
- * Delivers the dataset as a {@code Cursor} to be bound to the
- * panel view backed by it. This is used by the {@code DatasetHandler}
- * in response to a dataset request.
- */
- public final void deliverDataset(DatasetRequest request, Cursor cursor) {
- Log.d(LOGTAG, "Delivering request: " + request);
- final ViewState viewState = mViewStates.get(request.getViewIndex());
- if (viewState == null) {
- return;
- }
-
- switch (request.getType()) {
- case FILTER_PUSH:
- viewState.pushFilter(request.getFilterDetail());
- break;
- case FILTER_POP:
- viewState.popFilter();
- break;
- }
-
- final View activeView = viewState.getActiveView();
- if (activeView == null) {
- throw new IllegalStateException("No active view for view state: " + viewState.getIndex());
- }
-
- final ViewConfig viewConfig = viewState.getViewConfig();
-
- final View newView;
- if (cursor == null || cursor.getCount() == 0) {
- newView = createEmptyView(viewConfig);
- maybeSetDataset(activeView, null);
- } else {
- newView = createPanelView(viewConfig);
- maybeSetDataset(newView, cursor);
- }
-
- if (activeView != newView) {
- replacePanelView(activeView, newView);
- }
- }
-
- /**
- * Releases any references to the given dataset from all
- * existing panel views.
- */
- public final void releaseDataset(int viewIndex) {
- Log.d(LOGTAG, "Releasing dataset: " + viewIndex);
- final ViewState viewState = mViewStates.get(viewIndex);
- if (viewState == null) {
- return;
- }
-
- final View view = viewState.getView();
- if (view != null) {
- maybeSetDataset(view, null);
- }
- }
-
- /**
- * Requests a dataset to be loaded and bound to any existing
- * panel view backed by it.
- */
- protected final void requestDataset(DatasetRequest request) {
- Log.d(LOGTAG, "Requesting request: " + request);
- if (mViewStates.get(request.getViewIndex()) == null) {
- return;
- }
-
- mDatasetHandler.requestDataset(request);
- }
-
- /**
- * Releases any resources associated with a panel view.
- * e.g. close any associated {@code Cursor}.
- */
- protected final void resetDataset(int viewIndex) {
- Log.d(LOGTAG, "Resetting view with index: " + viewIndex);
- if (mViewStates.get(viewIndex) == null) {
- return;
- }
-
- mDatasetHandler.resetDataset(viewIndex);
- }
-
- /**
- * Factory method to create instance of panels from a given
- * {@code ViewConfig}. All panel views defined in {@code PanelConfig}
- * should be created using this method so that {@PanelLayout} can
- * keep track of panel views and their associated datasets.
- */
- protected final View createPanelView(ViewConfig viewConfig) {
- Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType());
-
- ViewState viewState = mViewStates.get(viewConfig.getIndex());
- if (viewState == null) {
- viewState = new ViewState(viewConfig);
- mViewStates.put(viewConfig.getIndex(), viewState);
- }
-
- View view = viewState.getView();
- if (view == null) {
- switch (viewConfig.getType()) {
- case LIST:
- view = new PanelListView(getContext(), viewConfig);
- break;
-
- case GRID:
- view = new PanelRecyclerView(getContext(), viewConfig);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName());
- }
-
- PanelView panelView = (PanelView) view;
- panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState));
- panelView.setOnKeyListener(new PanelKeyListener(viewState));
- panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE));
- return info;
- }
- });
-
- mContextMenuRegistry.register(view);
-
- if (view instanceof DatasetBacked) {
- DatasetBacked datasetBacked = (DatasetBacked) view;
- datasetBacked.setFilterManager(new PanelFilterManager(viewState));
-
- if (viewConfig.isRefreshEnabled()) {
- view = new PanelRefreshLayout(getContext(), view,
- mPanelConfig.getId(), viewConfig.getIndex());
- }
- }
-
- viewState.setView(view);
- }
-
- return view;
- }
-
- /**
- * Dispose any dataset references associated with the
- * given view.
- */
- protected final void disposePanelView(View view) {
- Log.d(LOGTAG, "Disposing panel view");
- final int count = mViewStates.size();
- for (int i = 0; i < count; i++) {
- final ViewState viewState = mViewStates.valueAt(i);
-
- if (viewState.getView() == view) {
- maybeSetDataset(view, null);
- mViewStates.remove(viewState.getIndex());
- break;
- }
- }
- }
-
- private void maybeSetDataset(View view, Cursor cursor) {
- if (view instanceof DatasetBacked) {
- final DatasetBacked dsb = (DatasetBacked) view;
- dsb.setDataset(cursor);
- }
- }
-
- private View createEmptyView(ViewConfig viewConfig) {
- Log.d(LOGTAG, "Creating empty view: " + viewConfig.getType());
-
- ViewState viewState = mViewStates.get(viewConfig.getIndex());
- if (viewState == null) {
- throw new IllegalStateException("No view state found for view index: " + viewConfig.getIndex());
- }
-
- View view = viewState.getEmptyView();
- if (view == null) {
- view = LayoutInflater.from(getContext()).inflate(R.layout.home_empty_panel, null);
-
- final EmptyViewConfig emptyViewConfig = viewConfig.getEmptyViewConfig();
-
- // XXX: Refactor this into a custom view (bug 985134)
- final String text = (emptyViewConfig == null) ? null : emptyViewConfig.getText();
- final TextView textView = (TextView) view.findViewById(R.id.home_empty_text);
- if (TextUtils.isEmpty(text)) {
- textView.setText(R.string.home_default_empty);
- } else {
- textView.setText(text);
- }
-
- final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl();
- final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image);
-
- if (TextUtils.isEmpty(imageUrl)) {
- imageView.setImageResource(R.drawable.icon_home_empty_firefox);
- } else {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .error(R.drawable.icon_home_empty_firefox)
- .into(imageView);
- }
-
- viewState.setEmptyView(view);
- }
-
- return view;
- }
-
- private void replacePanelView(View currentView, View newView) {
- final ViewGroup parent = (ViewGroup) currentView.getParent();
- parent.addView(newView, parent.indexOfChild(currentView), currentView.getLayoutParams());
- parent.removeView(currentView);
- }
-
- /**
- * Must be implemented by {@code PanelLayout} subclasses to define
- * what happens then the layout is first loaded. Should set initial
- * UI state and request any necessary datasets.
- */
- public abstract void load();
-
- /**
- * Represents a 'live' instance of a panel view associated with
- * the {@code PanelLayout}. Is responsible for tracking the history stack of filters.
- */
- protected class ViewState {
- private final ViewConfig mViewConfig;
- private SoftReference<View> mView;
- private SoftReference<View> mEmptyView;
- private LinkedList<FilterDetail> mFilterStack;
-
- public ViewState(ViewConfig viewConfig) {
- mViewConfig = viewConfig;
- mView = new SoftReference<View>(null);
- mEmptyView = new SoftReference<View>(null);
- }
-
- public ViewConfig getViewConfig() {
- return mViewConfig;
- }
-
- public int getIndex() {
- return mViewConfig.getIndex();
- }
-
- public View getView() {
- return mView.get();
- }
-
- public void setView(View view) {
- mView = new SoftReference<View>(view);
- }
-
- public View getEmptyView() {
- return mEmptyView.get();
- }
-
- public void setEmptyView(View view) {
- mEmptyView = new SoftReference<View>(view);
- }
-
- public View getActiveView() {
- final View view = getView();
- if (view != null && view.getParent() != null) {
- return view;
- }
-
- final View emptyView = getEmptyView();
- if (emptyView != null && emptyView.getParent() != null) {
- return emptyView;
- }
-
- return null;
- }
-
- public String getDatasetId() {
- return mViewConfig.getDatasetId();
- }
-
- public ItemHandler getItemHandler() {
- return mViewConfig.getItemHandler();
- }
-
- /**
- * Get the current filter that this view is displaying, or null if none.
- */
- public FilterDetail getCurrentFilter() {
- if (mFilterStack == null) {
- return null;
- } else {
- return mFilterStack.peek();
- }
- }
-
- /**
- * Get the previous filter that this view was displaying, or null if none.
- */
- public FilterDetail getPreviousFilter() {
- if (!canPopFilter()) {
- return null;
- }
-
- return mFilterStack.get(1);
- }
-
- /**
- * Adds a filter to the history stack for this view.
- */
- public void pushFilter(FilterDetail filter) {
- if (mFilterStack == null) {
- mFilterStack = new LinkedList<FilterDetail>();
-
- // Initialize with the initial filter.
- mFilterStack.push(new FilterDetail(mViewConfig.getFilter(),
- mPanelConfig.getTitle()));
- }
-
- mFilterStack.push(filter);
- }
-
- /**
- * Remove the most recent filter from the stack.
- *
- * @return whether the filter was popped
- */
- public boolean popFilter() {
- if (!canPopFilter()) {
- return false;
- }
-
- mFilterStack.pop();
- return true;
- }
-
- public boolean canPopFilter() {
- return (mFilterStack != null && mFilterStack.size() > 1);
- }
- }
-
- static class FilterDetail implements Parcelable {
- final String filter;
- final String title;
-
- private FilterDetail(Parcel in) {
- this.filter = in.readString();
- this.title = in.readString();
- }
-
- public FilterDetail(String filter, String title) {
- this.filter = filter;
- this.title = title;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(filter);
- dest.writeString(title);
- }
-
- public static final Creator<FilterDetail> CREATOR = new Creator<FilterDetail>() {
- @Override
- public FilterDetail createFromParcel(Parcel in) {
- return new FilterDetail(in);
- }
-
- @Override
- public FilterDetail[] newArray(int size) {
- return new FilterDetail[size];
- }
- };
- }
-
- /**
- * Pushes filter to {@code ViewState}'s stack and makes request for new filter value.
- */
- private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) {
- final int index = viewState.getIndex();
- final String datasetId = viewState.getDatasetId();
-
- mDatasetHandler.requestDataset(new DatasetRequest(index,
- DatasetRequest.Type.FILTER_PUSH,
- datasetId,
- filterDetail));
- }
-
- /**
- * Pops filter from {@code ViewState}'s stack and makes request for previous filter value.
- *
- * @return whether the filter has changed
- */
- private boolean popFilterOnView(ViewState viewState) {
- if (viewState.canPopFilter()) {
- final int index = viewState.getIndex();
- final String datasetId = viewState.getDatasetId();
- final FilterDetail filterDetail = viewState.getPreviousFilter();
-
- mDatasetHandler.requestDataset(new DatasetRequest(index,
- DatasetRequest.Type.FILTER_POP,
- datasetId,
- filterDetail));
-
- return true;
- } else {
- return false;
- }
- }
-
- public interface OnItemOpenListener {
- public void onItemOpen(String url, String title);
- }
-
- private class PanelOnItemOpenListener implements OnItemOpenListener {
- private final ViewState mViewState;
-
- public PanelOnItemOpenListener(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public void onItemOpen(String url, String title) {
- if (StringUtils.isFilterUrl(url)) {
- FilterDetail filterDetail = new FilterDetail(StringUtils.getFilterFromUrl(url), title);
- pushFilterOnView(mViewState, filterDetail);
- } else {
- EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
- if (mViewState.getItemHandler() == ItemHandler.INTENT) {
- flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT);
- }
-
- mUrlOpenListener.onUrlOpen(url, flags);
- }
- }
- }
-
- private class PanelKeyListener implements View.OnKeyListener {
- private final ViewState mViewState;
-
- public PanelKeyListener(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return popFilterOnView(mViewState);
- }
-
- return false;
- }
- }
-
- private class PanelFilterManager implements FilterManager {
- private final ViewState mViewState;
-
- public PanelFilterManager(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public FilterDetail getPreviousFilter() {
- return mViewState.getPreviousFilter();
- }
-
- @Override
- public boolean canGoBack() {
- return mViewState.canPopFilter();
- }
-
- @Override
- public void goBack() {
- popFilterOnView(mViewState);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java
deleted file mode 100644
index 505fb9b0d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ItemHandler;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
-import org.mozilla.gecko.home.PanelLayout.PanelView;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-import android.view.View;
-import android.widget.AdapterView;
-
-public class PanelListView extends HomeListView
- implements DatasetBacked, PanelView {
-
- private static final String LOGTAG = "GeckoPanelListView";
-
- private final ViewConfig viewConfig;
- private final PanelViewAdapter adapter;
- private final PanelViewItemHandler itemHandler;
- private OnItemOpenListener itemOpenListener;
-
- public PanelListView(Context context, ViewConfig viewConfig) {
- super(context);
-
- this.viewConfig = viewConfig;
- itemHandler = new PanelViewItemHandler();
-
- adapter = new PanelViewAdapter(context, viewConfig);
- setAdapter(adapter);
-
- setOnItemClickListener(new PanelListItemClickListener());
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- itemHandler.setOnItemOpenListener(itemOpenListener);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- itemHandler.setOnItemOpenListener(null);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- Log.d(LOGTAG, "Setting dataset: " + viewConfig.getDatasetId());
- adapter.swapCursor(cursor);
- }
-
- @Override
- public void setOnItemOpenListener(OnItemOpenListener listener) {
- itemHandler.setOnItemOpenListener(listener);
- itemOpenListener = listener;
- }
-
- @Override
- public void setFilterManager(FilterManager filterManager) {
- adapter.setFilterManager(filterManager);
- itemHandler.setFilterManager(filterManager);
- }
-
- private class PanelListItemClickListener implements AdapterView.OnItemClickListener {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- itemHandler.openItemAtPosition(adapter.getCursor(), position);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java
deleted file mode 100644
index 9145ab1e1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.PanelView;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemClickListener;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemLongClickListener;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-/**
- * RecyclerView implementation for grid home panels.
- */
-@SuppressLint("ViewConstructor") // View is only created from code
-public class PanelRecyclerView extends RecyclerView
- implements DatasetBacked, PanelView, OnItemClickListener, OnItemLongClickListener {
- private final PanelRecyclerViewAdapter adapter;
- private final GridLayoutManager layoutManager;
- private final PanelViewItemHandler itemHandler;
- private final float columnWidth;
- private final boolean autoFit;
- private final HomeConfig.ViewConfig viewConfig;
-
- private PanelLayout.OnItemOpenListener itemOpenListener;
- private HomeContextMenuInfo contextMenuInfo;
- private HomeContextMenuInfo.Factory contextMenuInfoFactory;
-
- public PanelRecyclerView(Context context, HomeConfig.ViewConfig viewConfig) {
- super(context);
-
- this.viewConfig = viewConfig;
-
- final Resources resources = context.getResources();
-
- int spanCount;
- if (viewConfig.getItemType() == HomeConfig.ItemType.ICON) {
- autoFit = false;
- spanCount = getResources().getInteger(R.integer.panel_icon_grid_view_columns);
- } else {
- autoFit = true;
- spanCount = 1;
- }
-
- columnWidth = resources.getDimension(R.dimen.panel_grid_view_column_width);
- layoutManager = new GridLayoutManager(context, spanCount);
- adapter = new PanelRecyclerViewAdapter(context, viewConfig);
- itemHandler = new PanelViewItemHandler();
-
- layoutManager.setSpanSizeLookup(new PanelSpanSizeLookup());
-
- setLayoutManager(layoutManager);
- setAdapter(adapter);
-
- int horizontalSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_horizontal_spacing);
- int verticalSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_vertical_spacing);
- int outerSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_outer_spacing);
-
- addItemDecoration(new SpacingDecoration(horizontalSpacing, verticalSpacing));
-
- setPadding(outerSpacing, outerSpacing, outerSpacing, outerSpacing);
- setClipToPadding(false);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this)
- .setOnItemLongClickListener(this);
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- super.onMeasure(widthSpec, heightSpec);
-
- if (autoFit) {
- // Adjust span based on space available (What GridView does when you say numColumns="auto_fit")
- final int spanCount = (int) Math.max(1, getMeasuredWidth() / columnWidth);
- layoutManager.setSpanCount(spanCount);
- }
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- itemHandler.setOnItemOpenListener(itemOpenListener);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- itemHandler.setOnItemOpenListener(null);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- adapter.swapCursor(cursor);
- }
-
- @Override
- public void setFilterManager(PanelLayout.FilterManager manager) {
- adapter.setFilterManager(manager);
- itemHandler.setFilterManager(manager);
- }
-
- @Override
- public void setOnItemOpenListener(PanelLayout.OnItemOpenListener listener) {
- itemOpenListener = listener;
- itemHandler.setOnItemOpenListener(listener);
- }
-
- @Override
- public HomeContextMenuInfo getContextMenuInfo() {
- return contextMenuInfo;
- }
-
- @Override
- public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory) {
- contextMenuInfoFactory = factory;
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- itemOpenListener.onItemOpen(viewConfig.getHeaderConfig().getUrl(), null);
- return;
- }
-
- position--;
- }
-
- itemHandler.openItemAtPosition(adapter.getCursor(), position);
- }
-
- @Override
- public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- final HomeConfig.HeaderConfig headerConfig = viewConfig.getHeaderConfig();
-
- final HomeContextMenuInfo info = new HomeContextMenuInfo(v, position, -1);
- info.url = headerConfig.getUrl();
- info.title = headerConfig.getUrl();
-
- contextMenuInfo = info;
- return showContextMenuForChild(this);
- }
-
- position--;
- }
-
- Cursor cursor = adapter.getCursor();
- cursor.moveToPosition(position);
-
- contextMenuInfo = contextMenuInfoFactory.makeInfoForCursor(recyclerView, position, -1, cursor);
- return showContextMenuForChild(this);
- }
-
- private class PanelSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
- @Override
- public int getSpanSize(int position) {
- if (position == 0 && viewConfig.hasHeaderConfig()) {
- return layoutManager.getSpanCount();
- }
-
- return 1;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java
deleted file mode 100644
index fa632bccd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-public class PanelRecyclerViewAdapter extends RecyclerView.Adapter<PanelRecyclerViewAdapter.PanelViewHolder> {
- private static final int VIEW_TYPE_ITEM = 0;
- private static final int VIEW_TYPE_BACK = 1;
- private static final int VIEW_TYPE_HEADER = 2;
-
- public static class PanelViewHolder extends RecyclerView.ViewHolder {
- public static PanelViewHolder create(View itemView) {
-
- // Wrap in a FrameLayout that will handle the highlight on touch
- FrameLayout frameLayout = (FrameLayout) LayoutInflater.from(itemView.getContext())
- .inflate(R.layout.panel_item_container, null);
-
- frameLayout.addView(itemView, 0, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-
- return new PanelViewHolder(frameLayout);
- }
-
- private PanelViewHolder(View itemView) {
- super(itemView);
- }
- }
-
- private final Context context;
- private final HomeConfig.ViewConfig viewConfig;
- private PanelLayout.FilterManager filterManager;
- private Cursor cursor;
-
- public PanelRecyclerViewAdapter(Context context, HomeConfig.ViewConfig viewConfig) {
- this.context = context;
- this.viewConfig = viewConfig;
- }
-
- public void setFilterManager(PanelLayout.FilterManager filterManager) {
- this.filterManager = filterManager;
- }
-
- private boolean isShowingBack() {
- return filterManager != null && filterManager.canGoBack();
- }
-
- public void swapCursor(Cursor cursor) {
- this.cursor = cursor;
-
- notifyDataSetChanged();
- }
-
- public Cursor getCursor() {
- return cursor;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (viewConfig.hasHeaderConfig() && position == 0) {
- return VIEW_TYPE_HEADER;
- } else if (isShowingBack() && position == getBackPosition()) {
- return VIEW_TYPE_BACK;
- } else {
- return VIEW_TYPE_ITEM;
- }
- }
-
- @Override
- public PanelViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_HEADER:
- return PanelViewHolder.create(new PanelHeaderView(context, viewConfig.getHeaderConfig()));
- case VIEW_TYPE_BACK:
- return PanelViewHolder.create(new PanelBackItemView(context, viewConfig.getBackImageUrl()));
- case VIEW_TYPE_ITEM:
- return PanelViewHolder.create(PanelItemView.create(context, viewConfig.getItemType()));
- default:
- throw new IllegalArgumentException("Unknown view type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(PanelViewHolder panelViewHolder, int position) {
- final View view = ((FrameLayout) panelViewHolder.itemView).getChildAt(0);
-
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- // Nothing to do here, the header is static
- return;
- }
- }
-
- if (isShowingBack()) {
- if (position == getBackPosition()) {
- final PanelBackItemView item = (PanelBackItemView) view;
- item.updateFromFilter(filterManager.getPreviousFilter());
- return;
- }
- }
-
- int actualPosition = position
- - (isShowingBack() ? 1 : 0)
- - (viewConfig.hasHeaderConfig() ? 1 : 0);
-
- cursor.moveToPosition(actualPosition);
-
- final PanelItemView panelItemView = (PanelItemView) view;
- panelItemView.updateFromCursor(cursor);
- }
-
- private int getBackPosition() {
- return viewConfig.hasHeaderConfig() ? 1 : 0;
- }
-
- @Override
- public int getItemCount() {
- if (cursor == null) {
- return 0;
- }
-
- return cursor.getCount()
- + (isShowingBack() ? 1 : 0)
- + (viewConfig.hasHeaderConfig() ? 1 : 0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
deleted file mode 100644
index d43a97f31..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.util.Log;
-import android.view.View;
-
-/**
- * Used to wrap a {@code DatasetBacked} ListView or GridView to give the child view swipe-to-refresh
- * capabilities.
- *
- * This view acts as a decorator to forward the {@code DatasetBacked} methods to the child view
- * while providing the refresh gesture support on top of it.
- */
-class PanelRefreshLayout extends SwipeRefreshLayout implements DatasetBacked {
- private static final String LOGTAG = "GeckoPanelRefreshLayout";
-
- private static final String JSON_KEY_PANEL_ID = "panelId";
- private static final String JSON_KEY_VIEW_INDEX = "viewIndex";
-
- private final String panelId;
- private final int viewIndex;
- private final DatasetBacked datasetBacked;
-
- /**
- * @param context Android context.
- * @param childView ListView or GridView. Must implement {@code DatasetBacked}.
- * @param panelId The ID from the {@code PanelConfig}.
- * @param viewIndex The index from the {@code ViewConfig}.
- */
- public PanelRefreshLayout(Context context, View childView, String panelId, int viewIndex) {
- super(context);
-
- if (!(childView instanceof DatasetBacked)) {
- throw new IllegalArgumentException("View must implement DatasetBacked to be refreshed");
- }
-
- this.panelId = panelId;
- this.viewIndex = viewIndex;
- this.datasetBacked = (DatasetBacked) childView;
-
- setOnRefreshListener(new RefreshListener());
- addView(childView);
-
- // Must be called after the child view has been added.
- setColorSchemeResources(R.color.fennec_ui_orange, R.color.action_orange);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- datasetBacked.setDataset(cursor);
- setRefreshing(false);
- }
-
- @Override
- public void setFilterManager(FilterManager manager) {
- datasetBacked.setFilterManager(manager);
- }
-
- private class RefreshListener implements OnRefreshListener {
- @Override
- public void onRefresh() {
- final JSONObject response = new JSONObject();
- try {
- response.put(JSON_KEY_PANEL_ID, panelId);
- response.put(JSON_KEY_VIEW_INDEX, viewIndex);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Could not create refresh message", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("HomePanels:RefreshView", response.toString());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java
deleted file mode 100644
index cf03c50c0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomeConfig.ItemType;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.CursorAdapter;
-import android.view.View;
-import android.view.ViewGroup;
-
-class PanelViewAdapter extends CursorAdapter {
- private static final int VIEW_TYPE_ITEM = 0;
- private static final int VIEW_TYPE_BACK = 1;
-
- private final ViewConfig viewConfig;
- private FilterManager filterManager;
- private final Context context;
-
- public PanelViewAdapter(Context context, ViewConfig viewConfig) {
- super(context, null, 0);
- this.context = context;
- this.viewConfig = viewConfig;
- }
-
- public void setFilterManager(FilterManager manager) {
- this.filterManager = manager;
- }
-
- @Override
- public final int getViewTypeCount() {
- return 2;
- }
-
- @Override
- public int getCount() {
- return super.getCount() + (isShowingBack() ? 1 : 0);
- }
-
- @Override
- public int getItemViewType(int position) {
- if (isShowingBack() && position == 0) {
- return VIEW_TYPE_BACK;
- } else {
- return VIEW_TYPE_ITEM;
- }
- }
-
- @Override
- public final View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = newView(parent.getContext(), position, parent);
- }
-
- bindView(convertView, position);
- return convertView;
- }
-
- private View newView(Context context, int position, ViewGroup parent) {
- if (getItemViewType(position) == VIEW_TYPE_BACK) {
- return new PanelBackItemView(context, viewConfig.getBackImageUrl());
- } else {
- return PanelItemView.create(context, viewConfig.getItemType());
- }
- }
-
- private void bindView(View view, int position) {
- if (isShowingBack()) {
- if (position == 0) {
- final PanelBackItemView item = (PanelBackItemView) view;
- item.updateFromFilter(filterManager.getPreviousFilter());
- return;
- }
-
- position--;
- }
-
- final Cursor cursor = getCursor(position);
- final PanelItemView item = (PanelItemView) view;
- item.updateFromCursor(cursor);
- }
-
- private boolean isShowingBack() {
- return filterManager != null && filterManager.canGoBack();
- }
-
- private final Cursor getCursor(int position) {
- final Cursor cursor = getCursor();
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- return cursor;
- }
-
- @Override
- public final void bindView(View view, Context context, Cursor cursor) {
- // Do nothing.
- }
-
- @Override
- public final View newView(Context context, Cursor cursor, ViewGroup parent) {
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java
deleted file mode 100644
index a69db0b41..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
-
-import android.database.Cursor;
-
-import java.util.EnumSet;
-
-class PanelViewItemHandler {
- private OnItemOpenListener mItemOpenListener;
- private FilterManager mFilterManager;
-
- public void setOnItemOpenListener(OnItemOpenListener listener) {
- mItemOpenListener = listener;
- }
-
- public void setFilterManager(FilterManager manager) {
- mFilterManager = manager;
- }
-
- /**
- * If item at this position is a back item, perform the go back action via the
- * {@code FilterManager}. Otherwise, prepare the url to be opened by the
- * {@code OnUrlOpenListener}.
- */
- public void openItemAtPosition(Cursor cursor, int position) {
- if (mFilterManager != null && mFilterManager.canGoBack()) {
- if (position == 0) {
- mFilterManager.goBack();
- return;
- }
-
- position--;
- }
-
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
- final String url = cursor.getString(urlIndex);
-
- int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
- final String title = cursor.getString(titleIndex);
-
- if (mItemOpenListener != null) {
- mItemOpenListener.onItemOpen(url, title);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java b/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java
deleted file mode 100644
index 230b1d329..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.db.BrowserDB.FilterFlags;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.ListView;
-
-/**
- * Dialog fragment that displays frecency search results, for pinning a site, in a GridView.
- */
-class PinSiteDialog extends DialogFragment {
- // Listener for url selection
- public static interface OnSiteSelectedListener {
- public void onSiteSelected(String url, String title);
- }
-
- // Cursor loader ID for search query
- private static final int LOADER_ID_SEARCH = 0;
-
- // Holds the current search term to use in the query
- private String mSearchTerm;
-
- // Adapter for the list of search results
- private SearchAdapter mAdapter;
-
- // Search entry
- private EditText mSearch;
-
- // Search results
- private ListView mList;
-
- // Callbacks used for the search loader
- private CursorLoaderCallbacks mLoaderCallbacks;
-
- // Bookmark selected listener
- private OnSiteSelectedListener mOnSiteSelectedListener;
-
- public static PinSiteDialog newInstance() {
- return new PinSiteDialog();
- }
-
- private PinSiteDialog() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // All list views are styled to look the same with a global activity theme.
- // If the style of the list changes, inflate it from an XML.
- return inflater.inflate(R.layout.pin_site_dialog, container, false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- mSearch = (EditText) view.findViewById(R.id.search);
- mSearch.addTextChangedListener(new TextWatcher() {
- @Override
- public void afterTextChanged(Editable s) {
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- setSearchTerm(mSearch.getText().toString());
- filter(mSearchTerm);
- }
- });
-
- mSearch.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode != KeyEvent.KEYCODE_ENTER || mOnSiteSelectedListener == null) {
- return false;
- }
-
- // If the user manually entered a search term or URL, wrap the value in
- // a special URI until we can get a valid URL for this bookmark.
- final String text = mSearch.getText().toString().trim();
- if (!TextUtils.isEmpty(text)) {
- final String url = StringUtils.encodeUserEnteredUrl(text);
- mOnSiteSelectedListener.onSiteSelected(url, text);
- dismiss();
- }
-
- return true;
- }
- });
-
- mSearch.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- // On rotation, the view gets destroyed and we could be in a race to get the dialog
- // and window (see bug 1072959).
- Dialog dialog = getDialog();
- if (dialog == null) {
- return;
- }
- Window window = dialog.getWindow();
- if (window == null) {
- return;
- }
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- }
- }
- });
-
- mList = (HomeListView) view.findViewById(R.id.list);
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mOnSiteSelectedListener != null) {
- final Cursor c = mAdapter.getCursor();
- if (c == null || !c.moveToPosition(position)) {
- return;
- }
-
- final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
- final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
- mOnSiteSelectedListener.onSiteSelected(url, title);
- }
-
- // Dismiss the fragment and the dialog.
- dismiss();
- }
- });
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final LoaderManager manager = getLoaderManager();
-
- // Initialize the search adapter
- mAdapter = new SearchAdapter(getActivity());
- mList.setAdapter(mAdapter);
-
- // Create callbacks before the initial loader is started
- mLoaderCallbacks = new CursorLoaderCallbacks();
-
- // Reconnect to the loader only if present
- manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks);
-
- // If there is a search term, put it in the text field
- if (!TextUtils.isEmpty(mSearchTerm)) {
- mSearch.setText(mSearchTerm);
- mSearch.selectAll();
- }
-
- // Always start with an empty filter
- filter("");
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- // Discard any additional site selection as the dialog
- // is getting destroyed (see bug 935542).
- setOnSiteSelectedListener(null);
- }
-
- public void setSearchTerm(String searchTerm) {
- mSearchTerm = searchTerm;
- }
-
- private void filter(String searchTerm) {
- // Restart loaders with the new search term
- SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH,
- mLoaderCallbacks, searchTerm,
- EnumSet.of(FilterFlags.EXCLUDE_PINNED_SITES));
- }
-
- public void setOnSiteSelectedListener(OnSiteSelectedListener listener) {
- mOnSiteSelectedListener = listener;
- }
-
- private static class SearchAdapter extends CursorAdapter {
- private final LayoutInflater mInflater;
-
- public SearchAdapter(Context context) {
- super(context, null, 0);
- mInflater = LayoutInflater.from(context);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- TwoLinePageRow row = (TwoLinePageRow) view;
- row.setShowIcons(false);
- row.updateFromCursor(cursor);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return (TwoLinePageRow) mInflater.inflate(R.layout.home_item_row, parent, false);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return SearchLoader.createInstance(getActivity(), args);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- mAdapter.swapCursor(c);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- mAdapter.swapCursor(null);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
deleted file mode 100755
index 3091f77da..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SessionParser;
-import org.mozilla.gecko.home.CombinedHistoryAdapter.RecentTabsUpdateHandler;
-import org.mozilla.gecko.home.CombinedHistoryPanel.PanelStateUpdateHandler;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.mozilla.gecko.home.CombinedHistoryItem.ItemType;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-
-public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem>
- implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder, NativeEventListener {
- private static final String LOGTAG = "GeckoRecentTabsAdapter";
-
- private static final int NAVIGATION_BACK_BUTTON_INDEX = 0;
-
- private static final String TELEMETRY_EXTRA_LAST_TIME = "recent_tabs_last_time";
- private static final String TELEMETRY_EXTRA_RECENTLY_CLOSED = "recent_closed_tabs";
- private static final String TELEMETRY_EXTRA_MIXED = "recent_tabs_mixed";
-
- // Recently closed tabs from Gecko.
- private ClosedTab[] recentlyClosedTabs;
- private boolean recentlyClosedTabsReceived = false;
-
- // "Tabs from last time".
- private ClosedTab[] lastSessionTabs;
-
- public static final class ClosedTab {
- public final String url;
- public final String title;
- public final String data;
-
- public ClosedTab(String url, String title, String data) {
- this.url = url;
- this.title = title;
- this.data = data;
- }
- }
-
- private final Context context;
- private final RecentTabsUpdateHandler recentTabsUpdateHandler;
- private final PanelStateUpdateHandler panelStateUpdateHandler;
-
- public RecentTabsAdapter(Context context,
- RecentTabsUpdateHandler recentTabsUpdateHandler,
- PanelStateUpdateHandler panelStateUpdateHandler) {
- this.context = context;
- this.recentTabsUpdateHandler = recentTabsUpdateHandler;
- this.panelStateUpdateHandler = panelStateUpdateHandler;
- recentlyClosedTabs = new ClosedTab[0];
- lastSessionTabs = new ClosedTab[0];
-
- readPreviousSessionData();
- }
-
- public void startListeningForClosedTabs() {
- EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
- GeckoAppShell.notifyObservers("ClosedTabs:StartNotifications", null);
- }
-
- public void stopListeningForClosedTabs() {
- GeckoAppShell.notifyObservers("ClosedTabs:StopNotifications", null);
- EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
- recentlyClosedTabsReceived = false;
- }
-
- public void startListeningForHistorySanitize() {
- EventDispatcher.getInstance().registerGeckoThreadListener(this, "Sanitize:Finished");
- }
-
- public void stopListeningForHistorySanitize() {
- EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Sanitize:Finished");
- }
-
- @Override
- public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
- switch (event) {
- case "ClosedTabs:Data":
- updateRecentlyClosedTabs(message);
- break;
- case "Sanitize:Finished":
- clearLastSessionData();
- break;
- }
- }
-
- private void updateRecentlyClosedTabs(NativeJSObject message) {
- final NativeJSObject[] tabs = message.getObjectArray("tabs");
- final int length = tabs.length;
-
- final ClosedTab[] closedTabs = new ClosedTab[length];
- for (int i = 0; i < length; i++) {
- final NativeJSObject tab = tabs[i];
- closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"), tab.getObject("data").toString());
- }
-
- // Only modify recentlyClosedTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = recentlyClosedTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- recentlyClosedTabs = closedTabs;
- recentlyClosedTabsReceived = true;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding/unhiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Update the "Recently closed" part of the tab list.
- updateTabsList(prevClosedTabsCount, recentlyClosedTabs.length, getFirstRecentTabIndex(), getLastRecentTabIndex());
- }
- });
- }
-
- private void readPreviousSessionData() {
- // If we happen to initialise before GeckoApp, waiting on either the main or the background
- // thread can lead to a deadlock, so we have to run on a separate thread instead.
- final Thread parseThread = new Thread(new Runnable() {
- @Override
- public void run() {
- // Make sure that the start up code has had a chance to update sessionstore.old as necessary.
- GeckoProfile.get(context).waitForOldSessionDataProcessing();
-
- final String jsonString = GeckoProfile.get(context).readPreviousSessionFile();
- if (jsonString == null) {
- // No previous session data.
- return;
- }
-
- final List<ClosedTab> parsedTabs = new ArrayList<>();
-
- new SessionParser() {
- @Override
- public void onTabRead(SessionTab tab) {
- final String url = tab.getUrl();
-
- // Don't show last tabs for about:home
- if (AboutPages.isAboutHome(url)) {
- return;
- }
-
- parsedTabs.add(new ClosedTab(url, tab.getTitle(), tab.getTabObject().toString()));
- }
- }.parse(jsonString);
-
- final ClosedTab[] closedTabs = parsedTabs.toArray(new ClosedTab[parsedTabs.size()]);
-
- // Only modify lastSessionTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = lastSessionTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- lastSessionTabs = closedTabs;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding/unhiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Update the "Tabs from last time" part of the tab list.
- updateTabsList(prevClosedTabsCount, lastSessionTabs.length, getFirstLastSessionTabIndex(), getLastLastSessionTabIndex());
- }
- });
- }
- }, "LastSessionTabsThread");
-
- parseThread.start();
- }
-
- private void clearLastSessionData() {
- final ClosedTab[] emptyLastSessionTabs = new ClosedTab[0];
-
- // Only modify mLastSessionTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = lastSessionTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- lastSessionTabs = emptyLastSessionTabs;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Handle the "tabs from last time" being cleared.
- if (prevClosedTabsCount > 0) {
- notifyItemRangeRemoved(getFirstLastSessionTabIndex(), prevClosedTabsCount);
- }
- }
- });
- }
-
- private void updateHeaderVisibility(boolean prevSectionHeaderVisibility, int prevSectionHeaderIndex) {
- if (prevSectionHeaderVisibility && !isSectionHeaderVisible()) {
- notifyItemRemoved(prevSectionHeaderIndex);
- } else if (!prevSectionHeaderVisibility && isSectionHeaderVisible()) {
- notifyItemInserted(getSectionHeaderIndex());
- }
- }
-
- /**
- * Updates the tab list as necessary to account for any changes in tab count in a particular data source.
- *
- * Since the session store only sends out full updates, we don't know for sure what has changed compared
- * to the last data set, so we can only animate if the tab count actually changes.
- *
- * @param prevClosedTabsCount The previous number of closed tabs from that data source.
- * @param closedTabsCount The current number of closed tabs contained in that data source.
- * @param firstTabListIndex The current position of that data source's first item in the RecyclerView.
- * @param lastTabListIndex The current position of that data source's last item in the RecyclerView.
- */
- private void updateTabsList(int prevClosedTabsCount, int closedTabsCount, int firstTabListIndex, int lastTabListIndex) {
- final int closedTabsCountChange = closedTabsCount - prevClosedTabsCount;
-
- if (closedTabsCountChange <= 0) {
- notifyItemRangeRemoved(lastTabListIndex + 1, -closedTabsCountChange); // Remove tabs from the bottom of the list.
- notifyItemRangeChanged(firstTabListIndex, closedTabsCount); // Update the contents of the remaining items.
- } else { // closedTabsCountChange > 0
- notifyItemRangeInserted(firstTabListIndex, closedTabsCountChange); // Add additional tabs at the top of the list.
- notifyItemRangeChanged(firstTabListIndex + closedTabsCountChange, prevClosedTabsCount); // Update any previous list items.
- }
- }
-
- public String restoreTabFromPosition(int position) {
- final List<String> dataList = new ArrayList<>(1);
- dataList.add(getClosedTabForPosition(position).data);
-
- final String telemetryExtra =
- position > getLastRecentTabIndex() ? TELEMETRY_EXTRA_LAST_TIME : TELEMETRY_EXTRA_RECENTLY_CLOSED;
-
- restoreSessionWithHistory(dataList);
-
- return telemetryExtra;
- }
-
- public String restoreAllTabs() {
- if (recentlyClosedTabs.length == 0 && lastSessionTabs.length == 0) {
- return null;
- }
-
- final List<String> dataList = new ArrayList<>(getClosedTabsCount());
- addTabDataToList(dataList, recentlyClosedTabs);
- addTabDataToList(dataList, lastSessionTabs);
-
- final String telemetryExtra = recentlyClosedTabs.length > 0 && lastSessionTabs.length > 0 ? TELEMETRY_EXTRA_MIXED :
- recentlyClosedTabs.length > 0 ? TELEMETRY_EXTRA_RECENTLY_CLOSED : TELEMETRY_EXTRA_LAST_TIME;
-
- restoreSessionWithHistory(dataList);
-
- return telemetryExtra;
- }
-
- private void addTabDataToList(List<String> dataList, ClosedTab[] closedTabs) {
- for (ClosedTab closedTab : closedTabs) {
- dataList.add(closedTab.data);
- }
- }
-
- private static void restoreSessionWithHistory(List<String> dataList) {
- final JSONObject json = new JSONObject();
- try {
- json.put("tabs", new JSONArray(dataList));
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
-
- GeckoAppShell.notifyObservers("Session:RestoreRecentTabs", json.toString());
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case NAVIGATION_BACK:
- view = inflater.inflate(R.layout.home_combined_back_item, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case SECTION_HEADER:
- view = inflater.inflate(R.layout.home_header_row, parent, false);
- return new CombinedHistoryItem.BasicItem(view);
-
- case CLOSED_TAB:
- view = inflater.inflate(R.layout.home_item_row, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
- }
- return null;
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem holder, final int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
-
- switch (itemType) {
- case SECTION_HEADER:
- ((TextView) holder.itemView).setText(context.getString(R.string.home_closed_tabs_title2));
- break;
-
- case CLOSED_TAB:
- final ClosedTab closedTab = getClosedTabForPosition(position);
- ((CombinedHistoryItem.HistoryItem) holder).bind(closedTab);
- break;
- }
- }
-
- @Override
- public int getItemCount() {
- int itemCount = 1; // NAVIGATION_BACK button is always visible.
-
- if (isSectionHeaderVisible()) {
- itemCount += 1;
- }
-
- itemCount += getClosedTabsCount();
-
- return itemCount;
- }
-
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == NAVIGATION_BACK_BUTTON_INDEX) {
- return ItemType.NAVIGATION_BACK;
- }
-
- if (position == getSectionHeaderIndex() && isSectionHeaderVisible()) {
- return ItemType.SECTION_HEADER;
- }
-
- return ItemType.CLOSED_TAB;
- }
-
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- public int getClosedTabsCount() {
- return recentlyClosedTabs.length + lastSessionTabs.length;
- }
-
- private boolean isSectionHeaderVisible() {
- return recentlyClosedTabs.length > 0 || lastSessionTabs.length > 0;
- }
-
- private int getSectionHeaderIndex() {
- return isSectionHeaderVisible() ?
- NAVIGATION_BACK_BUTTON_INDEX + 1 :
- NAVIGATION_BACK_BUTTON_INDEX;
- }
-
- private int getFirstRecentTabIndex() {
- return getSectionHeaderIndex() + 1;
- }
-
- private int getLastRecentTabIndex() {
- return getSectionHeaderIndex() + recentlyClosedTabs.length;
- }
-
- private int getFirstLastSessionTabIndex() {
- return getLastRecentTabIndex() + 1;
- }
-
- private int getLastLastSessionTabIndex() {
- return getLastRecentTabIndex() + lastSessionTabs.length;
- }
-
- /**
- * Get the closed tab corresponding to a RecyclerView list item.
- *
- * The Recent Tab folder combines two data sources, so if we want to get the ClosedTab object
- * behind a certain list item, we need to route this request to the corresponding data source
- * and also transform the global list position into a local array index.
- */
- private ClosedTab getClosedTabForPosition(int position) {
- final ClosedTab closedTab;
- if (position <= getLastRecentTabIndex()) { // Upper part of the list is "Recently closed tabs".
- closedTab = recentlyClosedTabs[position - getFirstRecentTabIndex()];
- } else { // Lower part is "Tabs from last time".
- closedTab = lastSessionTabs[position - getFirstLastSessionTabIndex()];
- }
-
- return closedTab;
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- final HomeContextMenuInfo info;
-
- switch (itemType) {
- case CLOSED_TAB:
- info = new HomeContextMenuInfo(view, position, -1);
- ClosedTab closedTab = getClosedTabForPosition(position);
- return populateChildInfoFromTab(info, closedTab);
- }
-
- return null;
- }
-
- protected static HomeContextMenuInfo populateChildInfoFromTab(HomeContextMenuInfo info, ClosedTab tab) {
- info.url = tab.url;
- info.title = tab.title;
- return info;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java b/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java
deleted file mode 100644
index 43497ae6c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.util.PrefUtils;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-/**
- * Encapsulate visual state maintained by the Remote Tabs home panel.
- * <p>
- * This state should persist across database updates by Sync and the like. This
- * state could be stored in a separate "clients_metadata" table and served by
- * the Tabs provider, but that is heavy-weight for what we want to achieve. Such
- * a scheme would require either an expensive table join, or a tricky
- * co-ordination between multiple cursors. In contrast, this is easy and cheap
- * enough to do on the main thread.
- * <p>
- * This state is "per SharedPreferences" object. In practice, there should exist
- * one state object per Gecko Profile; since we can't change profiles without
- * killing our process, this can be a static singleton.
- */
-public class RemoteTabsExpandableListState {
- private static final String PREF_COLLAPSED_CLIENT_GUIDS = "remote_tabs_collapsed_client_guids";
- private static final String PREF_HIDDEN_CLIENT_GUIDS = "remote_tabs_hidden_client_guids";
- private static final String PREF_SELECTED_CLIENT_GUID = "remote_tabs_selected_client_guid";
-
- protected final SharedPreferences sharedPrefs;
-
- // Synchronized by the state instance. The default is to expand a clients
- // tabs, so "not present" means "expanded".
- // Only accessed from the UI thread.
- protected final Set<String> collapsedClients;
-
- // Synchronized by the state instance. The default is to show a client, so
- // "not present" means "shown".
- // Only accessed from the UI thread.
- protected final Set<String> hiddenClients;
-
- // Synchronized by the state instance. The last user selected client guid.
- // The selectedClient may be invalid or null.
- protected String selectedClient;
-
- public RemoteTabsExpandableListState(SharedPreferences sharedPrefs) {
- if (null == sharedPrefs) {
- throw new IllegalArgumentException("sharedPrefs must not be null");
- }
- this.sharedPrefs = sharedPrefs;
-
- this.collapsedClients = getStringSet(PREF_COLLAPSED_CLIENT_GUIDS);
- this.hiddenClients = getStringSet(PREF_HIDDEN_CLIENT_GUIDS);
- this.selectedClient = sharedPrefs.getString(PREF_SELECTED_CLIENT_GUID, null);
- }
-
- /**
- * Extract a string set from shared preferences.
- * <p>
- * Nota bene: it is not OK to modify the set returned by {@link SharedPreferences#getStringSet(String, Set)}.
- *
- * @param pref to read from.
- * @returns string set; never null.
- */
- protected Set<String> getStringSet(String pref) {
- final Set<String> loaded = PrefUtils.getStringSet(sharedPrefs, pref, null);
- if (loaded != null) {
- return new HashSet<String>(loaded);
- } else {
- return new HashSet<String>();
- }
- }
-
- /**
- * Update client membership in a set.
- *
- * @param pref
- * to write updated set to.
- * @param clients
- * set to update membership in.
- * @param clientGuid
- * to update membership of.
- * @param isMember
- * whether the client is a member of the set.
- * @return true if the set of clients was modified.
- */
- protected boolean updateClientMembership(String pref, Set<String> clients, String clientGuid, boolean isMember) {
- final boolean modified;
- if (isMember) {
- modified = clients.add(clientGuid);
- } else {
- modified = clients.remove(clientGuid);
- }
-
- if (modified) {
- // This starts an asynchronous write. We don't care if we drop the
- // write, and we don't really care if we race between writes, since
- // we will return results from our in-memory cache.
- final Editor editor = sharedPrefs.edit();
- PrefUtils.putStringSet(editor, pref, clients);
- editor.apply();
- }
-
- return modified;
- }
-
- /**
- * Mark a client as collapsed.
- *
- * @param clientGuid
- * to update.
- * @param collapsed
- * whether the client is collapsed.
- * @return true if the set of collapsed clients was modified.
- */
- protected synchronized boolean setClientCollapsed(String clientGuid, boolean collapsed) {
- return updateClientMembership(PREF_COLLAPSED_CLIENT_GUIDS, collapsedClients, clientGuid, collapsed);
- }
-
- /**
- * Mark a client as the selected.
- *
- * @param clientGuid
- * to update.
- */
- protected synchronized void setClientAsSelected(String clientGuid) {
- if (hiddenClients.contains(clientGuid)) {
- selectedClient = null;
- } else {
- selectedClient = clientGuid;
- }
-
- final Editor editor = sharedPrefs.edit();
- editor.putString(PREF_SELECTED_CLIENT_GUID, selectedClient);
- editor.apply();
- }
-
- public synchronized boolean isClientCollapsed(String clientGuid) {
- return collapsedClients.contains(clientGuid);
- }
-
- /**
- * Mark a client as hidden.
- *
- * @param clientGuid
- * to update.
- * @param hidden
- * whether the client is hidden.
- * @return true if the set of hidden clients was modified.
- */
- protected synchronized boolean setClientHidden(String clientGuid, boolean hidden) {
- return updateClientMembership(PREF_HIDDEN_CLIENT_GUIDS, hiddenClients, clientGuid, hidden);
- }
-
- public synchronized boolean isClientHidden(String clientGuid) {
- return hiddenClients.contains(clientGuid);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java
deleted file mode 100644
index 9b2d2746a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.annotation.NonNull;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.R;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SearchEngine {
- public static final String LOG_TAG = "GeckoSearchEngine";
-
- public final String name; // Never null.
- public final String identifier; // Can be null.
-
- private final Bitmap icon;
- private volatile List<String> suggestions = new ArrayList<String>(); // Never null.
-
- public SearchEngine(final Context context, final JSONObject engineJSON) throws JSONException {
- if (engineJSON == null) {
- throw new IllegalArgumentException("Can't instantiate SearchEngine from null JSON.");
- }
-
- this.name = getString(engineJSON, "name");
- if (this.name == null) {
- throw new IllegalArgumentException("Cannot have an unnamed search engine.");
- }
-
- this.identifier = getString(engineJSON, "identifier");
-
- final String iconURI = getString(engineJSON, "iconURI");
- if (iconURI == null) {
- Log.w(LOG_TAG, "iconURI is null for search engine " + this.name);
- }
- final Bitmap tempIcon = BitmapUtils.getBitmapFromDataURI(iconURI);
-
- this.icon = (tempIcon != null) ? tempIcon : getDefaultFavicon(context);
- }
-
- private Bitmap getDefaultFavicon(final Context context) {
- return BitmapFactory.decodeResource(context.getResources(), R.drawable.search_icon_inactive);
- }
-
- private static String getString(JSONObject data, String key) throws JSONException {
- if (data.isNull(key)) {
- return null;
- }
- return data.getString(key);
- }
-
- /**
- * @return a non-null string suitable for use by FHR.
- */
- @NonNull
- public String getEngineIdentifier() {
- if (this.identifier != null) {
- return this.identifier;
- }
- if (this.name != null) {
- return "other-" + this.name;
- }
- return "other";
- }
-
- public boolean hasSuggestions() {
- return !this.suggestions.isEmpty();
- }
-
- public int getSuggestionsCount() {
- return this.suggestions.size();
- }
-
- public Iterable<String> getSuggestions() {
- return this.suggestions;
- }
-
- public void setSuggestions(List<String> suggestions) {
- if (suggestions == null) {
- this.suggestions = new ArrayList<String>();
- return;
- }
- this.suggestions = suggestions;
- }
-
- public Bitmap getIcon() {
- return this.icon;
- }
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java
deleted file mode 100644
index be5b3b461..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import org.mozilla.gecko.R;
-
-import java.util.Collections;
-import java.util.List;
-
-public class SearchEngineAdapter
- extends RecyclerView.Adapter<SearchEngineAdapter.SearchEngineViewHolder> {
-
- private static final String LOGTAG = SearchEngineAdapter.class.getSimpleName();
-
- private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
- private static final int VIEW_TYPE_LABEL = 1;
- private final Context mContext;
-
- private int mContainerWidth;
- private List<SearchEngine> mSearchEngines = Collections.emptyList();
-
- public void setSearchEngines(List<SearchEngine> searchEngines) {
- mSearchEngines = searchEngines;
- notifyDataSetChanged();
- }
-
- /**
- * The container width is used for setting the appropriate calculated amount of width that
- * a search engine icon can have. This varies depending on the space available in the
- * {@link SearchEngineBar}. The setter exists for this attribute, in creating the view in the
- * adapter after said calculation is done when the search bar is created.
- * @param iconContainerWidth Width of each search icon.
- */
- void setIconContainerWidth(int iconContainerWidth) {
- mContainerWidth = iconContainerWidth;
- }
-
- public static class SearchEngineViewHolder extends RecyclerView.ViewHolder {
- final private ImageView faviconView;
-
- public void bindItem(SearchEngine searchEngine) {
- faviconView.setImageBitmap(searchEngine.getIcon());
- final String desc = itemView.getResources().getString(R.string.search_bar_item_desc,
- searchEngine.getEngineIdentifier());
- itemView.setContentDescription(desc);
- }
-
- public SearchEngineViewHolder(View itemView) {
- super(itemView);
- faviconView = (ImageView) itemView.findViewById(R.id.search_engine_icon);
- }
- }
-
- public SearchEngineAdapter(Context context) {
- mContext = context;
- }
-
- @Override
- public int getItemViewType(int position) {
- return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
- }
-
- public SearchEngine getItem(int position) {
- // We omit the first position which is where the label currently is.
- return position == 0 ? null : mSearchEngines.get(position - 1);
- }
-
- @Override
- public SearchEngineViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_LABEL:
- return new SearchEngineViewHolder(createLabelView(parent));
- case VIEW_TYPE_SEARCH_ENGINE:
- return new SearchEngineViewHolder(createSearchEngineView(parent));
- default:
- throw new IllegalArgumentException("Unknown view type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(SearchEngineViewHolder holder, int position) {
- if (position != 0) {
- holder.bindItem(getItem(position));
- }
- }
-
- @Override
- public int getItemCount() {
- return mSearchEngines.size() + 1;
- }
-
- private View createLabelView(ViewGroup parent) {
- View view = LayoutInflater.from(mContext)
- .inflate(R.layout.search_engine_bar_label, parent, false);
- final Drawable icon = DrawableCompat.wrap(
- ContextCompat.getDrawable(mContext, R.drawable.search_icon_active).mutate());
- DrawableCompat.setTint(icon, ContextCompat.getColor(mContext, R.color.disabled_grey));
-
- final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
- iconView.setImageDrawable(icon);
- return view;
- }
-
- private View createSearchEngineView(ViewGroup parent) {
- View view = LayoutInflater.from(mContext)
- .inflate(R.layout.search_engine_bar_item, parent, false);
-
- ViewGroup.LayoutParams params = view.getLayoutParams();
- params.width = mContainerWidth;
- view.setLayoutParams(params);
-
- return view;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java
deleted file mode 100644
index 6a6509bcb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.View;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.List;
-
-public class SearchEngineBar extends RecyclerView
- implements RecyclerViewClickSupport.OnItemClickListener {
- private static final String LOGTAG = SearchEngineBar.class.getSimpleName();
-
- private static final float ICON_CONTAINER_MIN_WIDTH_DP = 72;
- private static final float LABEL_CONTAINER_WIDTH_DP = 48;
-
- public interface OnSearchBarClickListener {
- void onSearchBarClickListener(SearchEngine searchEngine);
- }
-
- private final SearchEngineAdapter mAdapter;
- private final LinearLayoutManager mLayoutManager;
- private final Paint mDividerPaint;
- private final float mMinIconContainerWidth;
- private final float mDividerHeight;
- private final int mLabelContainerWidth;
-
- private int mIconContainerWidth;
- private OnSearchBarClickListener mOnSearchBarClickListener;
-
- public SearchEngineBar(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- mDividerPaint = new Paint();
- mDividerPaint.setColor(ContextCompat.getColor(context, R.color.toolbar_divider_grey));
- mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
- final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mMinIconContainerWidth = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
- mDividerHeight = context.getResources().getDimension(R.dimen.page_row_divider_height);
- mLabelContainerWidth = Math.round(TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics));
-
- mIconContainerWidth = Math.round(mMinIconContainerWidth);
-
- mAdapter = new SearchEngineAdapter(context);
- mAdapter.setIconContainerWidth(mIconContainerWidth);
- mLayoutManager = new LinearLayoutManager(context);
- mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
-
- setAdapter(mAdapter);
- setLayoutManager(mLayoutManager);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this);
- }
-
- public void setSearchEngines(List<SearchEngine> searchEngines) {
- mAdapter.setSearchEngines(searchEngines);
- }
-
- public void setOnSearchBarClickListener(OnSearchBarClickListener listener) {
- mOnSearchBarClickListener = listener;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int searchEngineCount = mAdapter.getItemCount() - 1;
-
- if (searchEngineCount > 0) {
- final int availableWidth = getMeasuredWidth() - mLabelContainerWidth;
-
- if (searchEngineCount * mMinIconContainerWidth <= availableWidth) {
- // All search engines fit int: So let's just display all.
- mIconContainerWidth = (int) mMinIconContainerWidth;
- } else {
- // If only (n) search engines fit into the available space then display only (x)
- // search engines with (x) picked so that the last search engine will be cut-off
- // (we only display half of it) to show the ability to scroll this view.
-
- final double searchEnginesToDisplay = Math.floor((availableWidth / mMinIconContainerWidth) - 0.5) + 0.5;
- // Use all available width and spread search engine icons
- mIconContainerWidth = (int) (availableWidth / searchEnginesToDisplay);
- }
-
- mAdapter.setIconContainerWidth(mIconContainerWidth);
- }
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- canvas.drawRect(0, 0, getWidth(), mDividerHeight, mDividerPaint);
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (mOnSearchBarClickListener == null) {
- throw new IllegalStateException(
- OnSearchBarClickListener.class.getSimpleName() + " is not initializer."
- );
- }
-
- if (position == 0) {
- final Intent settingsIntent = new Intent(getContext(), GeckoPreferences.class);
- GeckoPreferences.setResourceToOpen(settingsIntent, "preferences_search");
- getContext().startActivity(settingsIntent);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "searchenginebar-settings");
- return;
- }
-
- final SearchEngine searchEngine = mAdapter.getItem(position);
- mOnSearchBarClickListener.onSearchBarClickListener(searchEngine);
- }
-
- /**
- * We manually add the override for getAdapter because we see this method getting stripped
- * out during compile time by aggressive proguard rules.
- */
- @RobocopTarget
- @Override
- public SearchEngineAdapter getAdapter() {
- return mAdapter;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
deleted file mode 100644
index 5b97a8f5f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
-import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.widget.AnimatedHeightLayout;
-import org.mozilla.gecko.widget.FaviconView;
-import org.mozilla.gecko.widget.FlowLayout;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.graphics.Typeface;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-
-class SearchEngineRow extends AnimatedHeightLayout {
- // Duration for fade-in animation
- private static final int ANIMATION_DURATION = 250;
-
- // Inner views
- private final FlowLayout mSuggestionView;
- private final FaviconView mIconView;
- private final LinearLayout mUserEnteredView;
- private final TextView mUserEnteredTextView;
-
- // Inflater used when updating from suggestions
- private final LayoutInflater mInflater;
-
- // Search engine associated with this view
- private SearchEngine mSearchEngine;
-
- // Event listeners for suggestion views
- private final OnClickListener mClickListener;
- private final OnLongClickListener mLongClickListener;
-
- // On URL open listener
- private OnUrlOpenListener mUrlOpenListener;
-
- // On search listener
- private OnSearchListener mSearchListener;
-
- // On edit suggestion listener
- private OnEditSuggestionListener mEditSuggestionListener;
-
- // Selected suggestion view
- private int mSelectedView;
-
- // android:backgroundTint only works in Android 21 and higher so we can't do this statically in the xml
- private Drawable mSearchHistorySuggestionIcon;
-
- // Maximums for suggestions
- private int mMaxSavedSuggestions;
- private int mMaxSearchSuggestions;
-
- private final List<Integer> mOccurrences = new ArrayList<Integer>();
-
- public SearchEngineRow(Context context) {
- this(context, null);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final String suggestion = getSuggestionTextFromView(v);
-
- // If we're not clicking the user-entered view (the first suggestion item)
- // and the search matches a URL pattern, go to that URL. Otherwise, do a
- // search for the term.
- if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, true)) {
- if (mUrlOpenListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url");
-
- mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
- }
- } else if (mSearchListener != null) {
- if (v == mUserEnteredView) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- } else {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, (String) v.getTag());
- }
- mSearchListener.onSearch(mSearchEngine, suggestion, TelemetryContract.Method.SUGGESTION);
- }
- }
- };
-
- mLongClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mEditSuggestionListener != null) {
- final String suggestion = getSuggestionTextFromView(v);
- mEditSuggestionListener.onEditSuggestion(suggestion);
- return true;
- }
-
- return false;
- }
- };
-
- mInflater = LayoutInflater.from(context);
- mInflater.inflate(R.layout.search_engine_row, this);
-
- mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout);
- mIconView = (FaviconView) findViewById(R.id.suggestion_icon);
-
- // User-entered search term is first suggestion
- mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered);
- mUserEnteredView.setOnClickListener(mClickListener);
-
- mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
- mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
-
- // Suggestion limits
- mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);
- mMaxSearchSuggestions = getResources().getInteger(R.integer.max_search_suggestions);
- }
-
- private void setDescriptionOnSuggestion(View v, String suggestion) {
- v.setContentDescription(getResources().getString(R.string.suggestion_for_engine,
- mSearchEngine.name, suggestion));
- }
-
- private String getSuggestionTextFromView(View v) {
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- return suggestionText.getText().toString();
- }
-
- /**
- * Finds all occurrences of pattern in string and returns a list of the starting indices
- * of each occurrence.
- *
- * @param pattern The pattern that is searched for
- * @param string The string where we search for the pattern
- */
- private void refreshOccurrencesWith(String pattern, String string) {
- mOccurrences.clear();
-
- // Don't try to search for an empty string - String.indexOf will return 0, which would result
- // in us iterating with lastIndexOfMatch = 0, which eventually results in an OOM.
- if (TextUtils.isEmpty(pattern)) {
- return;
- }
-
- final int patternLength = pattern.length();
-
- int indexOfMatch = 0;
- int lastIndexOfMatch = 0;
- while (indexOfMatch != -1) {
- indexOfMatch = string.indexOf(pattern, lastIndexOfMatch);
- lastIndexOfMatch = indexOfMatch + patternLength;
- if (indexOfMatch != -1) {
- mOccurrences.add(indexOfMatch);
- }
- }
- }
-
- /**
- * Sets the content for the suggestion view.
- *
- * If the suggestion doesn't contain mUserSearchTerm, nothing is made bold.
- * All instances of mUserSearchTerm in the suggestion are not bold.
- *
- * @param v The View that needs to be populated
- * @param suggestion The suggestion text that will be placed in the view
- * @param isUserSavedSearch whether the suggestion is from history or not
- */
- private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
- final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
- if (isUserSavedSearch) {
- historyIcon.setImageDrawable(mSearchHistorySuggestionIcon);
- historyIcon.setVisibility(View.VISIBLE);
- } else {
- historyIcon.setVisibility(View.GONE);
- }
-
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- final String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- final int searchTermLength = searchTerm.length();
- refreshOccurrencesWith(searchTerm, suggestion);
- if (mOccurrences.size() > 0) {
- final SpannableStringBuilder sb = new SpannableStringBuilder(suggestion);
- int nextStartSpanIndex = 0;
- // Done to make sure that the stretch of text after the last occurrence, till the end of the suggestion, is made bold
- mOccurrences.add(suggestion.length());
- for (int occurrence : mOccurrences) {
- // Even though they're the same style, SpannableStringBuilder will interpret there as being only one Span present if we re-use a StyleSpan
- StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
- sb.setSpan(boldSpan, nextStartSpanIndex, occurrence, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- nextStartSpanIndex = occurrence + searchTermLength;
- }
- mOccurrences.clear();
- suggestionText.setText(sb);
- } else {
- suggestionText.setText(suggestion);
- }
-
- setDescriptionOnSuggestion(suggestionText, suggestion);
- }
-
- /**
- * Perform a search for the user-entered term.
- */
- public void performUserEnteredSearch() {
- String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- if (mSearchListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- mSearchListener.onSearch(mSearchEngine, searchTerm, TelemetryContract.Method.SUGGESTION);
- }
- }
-
- public void setSearchTerm(String searchTerm) {
- mUserEnteredTextView.setText(searchTerm);
-
- // mSearchEngine is not set in the first call to this method; the content description
- // is instead initially set in updateSuggestions().
- if (mSearchEngine != null) {
- setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm);
- }
- }
-
- public void setOnUrlOpenListener(OnUrlOpenListener listener) {
- mUrlOpenListener = listener;
- }
-
- public void setOnSearchListener(OnSearchListener listener) {
- mSearchListener = listener;
- }
-
- public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
- mEditSuggestionListener = listener;
- }
-
- private void bindSuggestionView(String suggestion, boolean animate, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch, String telemetryTag) {
- final View suggestionItem;
-
- // Reuse suggestion views from recycled view, if possible.
- if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
- suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
- suggestionItem.setVisibility(View.VISIBLE);
- } else {
- suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
-
- suggestionItem.setOnClickListener(mClickListener);
- suggestionItem.setOnLongClickListener(mLongClickListener);
-
- suggestionItem.setTag(telemetryTag);
-
- mSuggestionView.addView(suggestionItem);
- }
-
- setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
-
- if (animate) {
- AlphaAnimation anim = new AlphaAnimation(0, 1);
- anim.setDuration(ANIMATION_DURATION);
- anim.setStartOffset(previousSuggestionChildIndex * ANIMATION_DURATION);
- suggestionItem.startAnimation(anim);
- }
- }
-
- private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
- // Hide extra suggestions that have been recycled.
- for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
- mSuggestionView.getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- /**
- * Displays search suggestions from previous searches.
- *
- * @param savedSuggestions The List to iterate over for saved search suggestions to display. This function does not
- * enforce a ui maximum or filter. It will show all the suggestions in this list.
- * @param suggestionStartIndex global index of where to start adding suggestion "buttons" in the search engine row. Also
- * acts as a counter for total number of suggestions visible.
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- */
- private void updateFromSavedSearches(List<String> savedSuggestions, boolean animate, int suggestionStartIndex, int recycledSuggestionCount) {
- if (savedSuggestions == null || savedSuggestions.isEmpty()) {
- hideRecycledSuggestions(suggestionStartIndex, recycledSuggestionCount);
- return;
- }
-
- final int numSavedSearches = savedSuggestions.size();
- int indexOfPreviousSuggestion = 0;
- for (int i = 0; i < numSavedSearches; i++) {
- String telemetryTag = "history." + i;
- final String suggestion = savedSuggestions.get(i);
- indexOfPreviousSuggestion = suggestionStartIndex + i;
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, indexOfPreviousSuggestion, true, telemetryTag);
- }
-
- hideRecycledSuggestions(indexOfPreviousSuggestion + 1, recycledSuggestionCount);
- }
-
- /**
- * Displays suggestions supplied by the search engine, relative to number of suggestions from search history.
- *
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- * @param savedSuggestionCount how many saved searches this searchTerm has
- * @return the global count of how many suggestions have been bound/shown in the search engine row
- */
- private int updateFromSearchEngine(boolean animate, List<String> searchEngineSuggestions, int recycledSuggestionCount, int savedSuggestionCount) {
- int maxSuggestions = mMaxSearchSuggestions;
- // If there are less than max saved searches on phones, fill the space with more search engine suggestions
- if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
- maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
- }
-
- final int numSearchEngineSuggestions = searchEngineSuggestions.size();
- int relativeIndex;
- for (relativeIndex = 0; relativeIndex < numSearchEngineSuggestions; relativeIndex++) {
- if (relativeIndex == maxSuggestions) {
- break;
- }
-
- // Since the search engine suggestions are listed first, their relative index is their global index
- String telemetryTag = "engine." + relativeIndex;
- final String suggestion = searchEngineSuggestions.get(relativeIndex);
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, relativeIndex, false, telemetryTag);
- }
-
- hideRecycledSuggestions(relativeIndex + 1, recycledSuggestionCount);
-
- // Make sure mSelectedView is still valid.
- if (mSelectedView >= mSuggestionView.getChildCount()) {
- mSelectedView = mSuggestionView.getChildCount() - 1;
- }
-
- return relativeIndex;
- }
-
- /**
- * Updates the whole suggestions UI, the search engine UI, suggestions from the default search engine,
- * and suggestions from search history.
- *
- * This can be called before the opt-in permission prompt is shown or set.
- * Even if both suggestion types are disabled, we need to update the search engine, its image, and the content description.
- *
- * @param searchSuggestionsEnabled whether or not suggestions from the default search engine are enabled
- * @param searchEngine the search engine to use throughout the SearchEngineRow class
- * @param rawSearchHistorySuggestions search history suggestions
- * @param animate whether or not to use animations
- **/
- public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, @Nullable List<String> rawSearchHistorySuggestions, boolean animate) {
- mSearchEngine = searchEngine;
- // Set the search engine icon (e.g., Google) for the row.
-
- mIconView.updateAndScaleImage(IconResponse.create(mSearchEngine.getIcon()));
- // Set the initial content description.
- setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
-
- final int recycledSuggestionCount = mSuggestionView.getChildCount();
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
-
- // Remove duplicates of search engine suggestions from saved searches.
- List<String> searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList<String>();
-
- // Filter out URLs and long search suggestions
- Iterator<String> searchHistoryIterator = searchHistorySuggestions.iterator();
- while (searchHistoryIterator.hasNext()) {
- final String currentSearchHistory = searchHistoryIterator.next();
-
- if (currentSearchHistory.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", currentSearchHistory)) {
- searchHistoryIterator.remove();
- }
- }
-
-
- List<String> searchEngineSuggestions = new ArrayList<String>();
- for (String suggestion : searchEngine.getSuggestions()) {
- searchHistorySuggestions.remove(suggestion);
- searchEngineSuggestions.add(suggestion);
- }
- // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen
- // space is more precious.
- searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView));
-
- // Trim the history suggestions down to the maximum allowed.
- if (searchHistorySuggestions.size() >= mMaxSavedSuggestions) {
- // The second index to subList() is exclusive, so this looks like an off by one error but it is not.
- searchHistorySuggestions = searchHistorySuggestions.subList(0, mMaxSavedSuggestions);
- }
- final int searchHistoryCount = searchHistorySuggestions.size();
-
- if (searchSuggestionsEnabled && savedSearchesEnabled) {
- final int suggestionViewCount = updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, searchHistoryCount);
- updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount);
- } else if (savedSearchesEnabled) {
- updateFromSavedSearches(searchHistorySuggestions, animate, 0, recycledSuggestionCount);
- } else if (searchSuggestionsEnabled) {
- updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, 0);
- } else {
- // The current search term is treated separately from the suggestions list, hence we can
- // recycle ALL suggestion items here. (We always show the current search term, i.e. 1 item,
- // in front of the search engine suggestions and/or the search history.)
- hideRecycledSuggestions(0, recycledSuggestionCount);
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
-
- if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1);
- if (nextSuggestion != null) {
- changeSelectedSuggestion(suggestion, nextSuggestion);
- mSelectedView++;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1);
- if (prevSuggestion != null) {
- changeSelectedSuggestion(suggestion, prevSuggestion);
- mSelectedView--;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_BUTTON_A:
- // TODO: handle long pressing for editing suggestions
- return suggestion.performClick();
- }
-
- return false;
- }
-
- private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) {
- oldSuggestion.setDuplicateParentStateEnabled(false);
- newSuggestion.setDuplicateParentStateEnabled(true);
- oldSuggestion.refreshDrawableState();
- newSuggestion.refreshDrawableState();
- }
-
- public void onSelected() {
- mSelectedView = 0;
- mUserEnteredView.setDuplicateParentStateEnabled(true);
- mUserEnteredView.refreshDrawableState();
- }
-
- public void onDeselected() {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
- suggestion.setDuplicateParentStateEnabled(false);
- suggestion.refreshDrawableState();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java
deleted file mode 100644
index f7b5b6586..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.FilterFlags;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-
-/**
- * Encapsulates the implementation of the search cursor loader.
- */
-class SearchLoader {
- public static final String LOGTAG = "GeckoSearchLoader";
-
- private static final String KEY_SEARCH_TERM = "search_term";
- private static final String KEY_FILTER_FLAGS = "flags";
-
- private SearchLoader() {
- }
-
- @SuppressWarnings("unchecked")
- public static Loader<Cursor> createInstance(Context context, Bundle args) {
- if (args != null) {
- final String searchTerm = args.getString(KEY_SEARCH_TERM);
- final EnumSet<FilterFlags> flags =
- (EnumSet<FilterFlags>) args.getSerializable(KEY_FILTER_FLAGS);
- return new SearchCursorLoader(context, searchTerm, flags);
- } else {
- return new SearchCursorLoader(context, "", EnumSet.noneOf(FilterFlags.class));
- }
- }
-
- private static Bundle createArgs(String searchTerm, EnumSet<FilterFlags> flags) {
- Bundle args = new Bundle();
- args.putString(SearchLoader.KEY_SEARCH_TERM, searchTerm);
- args.putSerializable(SearchLoader.KEY_FILTER_FLAGS, flags);
-
- return args;
- }
-
- public static void init(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm) {
- init(manager, loaderId, callbacks, searchTerm, EnumSet.noneOf(FilterFlags.class));
- }
-
- public static void init(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm,
- EnumSet<FilterFlags> flags) {
- final Bundle args = createArgs(searchTerm, flags);
- manager.initLoader(loaderId, args, callbacks);
- }
-
- public static void restart(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm) {
- restart(manager, loaderId, callbacks, searchTerm, EnumSet.noneOf(FilterFlags.class));
- }
-
- public static void restart(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm,
- EnumSet<FilterFlags> flags) {
- final Bundle args = createArgs(searchTerm, flags);
- manager.restartLoader(loaderId, args, callbacks);
- }
-
- public static class SearchCursorLoader extends SimpleCursorLoader {
- private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_SEARCH_LOADER_TIME_MS";
-
- // Max number of search results.
- private static final int SEARCH_LIMIT = 100;
-
- // The target search term associated with the loader.
- private final String mSearchTerm;
-
- // The filter flags associated with the loader.
- private final EnumSet<FilterFlags> mFlags;
- private final GeckoProfile mProfile;
-
- public SearchCursorLoader(Context context, String searchTerm, EnumSet<FilterFlags> flags) {
- super(context);
- mSearchTerm = searchTerm;
- mFlags = flags;
- mProfile = GeckoProfile.get(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final long start = SystemClock.uptimeMillis();
- final Cursor cursor = BrowserDB.from(mProfile).filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT, mFlags);
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
- return cursor;
- }
-
- public String getSearchTerm() {
- return mSearchTerm;
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java
deleted file mode 100644
index b8889c033..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * This is an adapted version of Android's original CursorLoader
- * without all the ContentProvider-specific bits.
- *
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.content.AsyncTaskLoader;
-
-import org.mozilla.gecko.GeckoApplication;
-
-/**
- * A copy of the framework's {@link android.content.CursorLoader} that
- * instead allows the caller to load the Cursor themselves via the abstract
- * {@link #loadCursor()} method, rather than calling out to a ContentProvider via
- * class methods.
- *
- * For new code, prefer {@link android.content.CursorLoader} (see @deprecated).
- *
- * This was originally created to re-use existing code which loaded Cursors manually.
- *
- * @deprecated since the framework provides an implementation, we'd like to eventually remove
- * this class to reduce maintenance burden. Originally planned for bug 1239491, but
- * it'd be more efficient to do this over time, rather than all at once.
- */
-@Deprecated
-public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
- final ForceLoadContentObserver mObserver;
- Cursor mCursor;
-
- public SimpleCursorLoader(Context context) {
- super(context);
- mObserver = new ForceLoadContentObserver();
- }
-
- /**
- * Loads the target cursor for this loader. This method is called
- * on a worker thread.
- */
- protected abstract Cursor loadCursor();
-
- /* Runs on a worker thread */
- @Override
- public Cursor loadInBackground() {
- Cursor cursor = loadCursor();
-
- if (cursor != null) {
- // Ensure the cursor window is filled
- cursor.getCount();
- cursor.registerContentObserver(mObserver);
- }
-
- return cursor;
- }
-
- /* Runs on the UI thread */
- @Override
- public void deliverResult(Cursor cursor) {
- if (isReset()) {
- // An async query came in while the loader is stopped
- if (cursor != null) {
- cursor.close();
- }
-
- return;
- }
-
- Cursor oldCursor = mCursor;
- mCursor = cursor;
-
- if (isStarted()) {
- super.deliverResult(cursor);
- }
-
- if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
- oldCursor.close();
-
- // Trying to read from the closed cursor will cause crashes, hence we should make
- // sure that no adapters/LoaderCallbacks are holding onto this cursor.
- GeckoApplication.getRefWatcher(getContext()).watch(oldCursor);
- }
- }
-
- /**
- * Starts an asynchronous load of the list data. When the result is ready the callbacks
- * will be called on the UI thread. If a previous load has been completed and is still valid
- * the result may be passed to the callbacks immediately.
- *
- * Must be called from the UI thread
- */
- @Override
- protected void onStartLoading() {
- if (mCursor != null) {
- deliverResult(mCursor);
- }
-
- if (takeContentChanged() || mCursor == null) {
- forceLoad();
- }
- }
-
- /**
- * Must be called from the UI thread
- */
- @Override
- protected void onStopLoading() {
- // Attempt to cancel the current load task if possible.
- cancelLoad();
- }
-
- @Override
- public void onCanceled(Cursor cursor) {
- if (cursor != null && !cursor.isClosed()) {
- cursor.close();
- }
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped
- onStopLoading();
-
- if (mCursor != null && !mCursor.isClosed()) {
- mCursor.close();
- }
-
- mCursor = null;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java b/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java
deleted file mode 100644
index 039b65e82..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.graphics.Rect;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-public class SpacingDecoration extends RecyclerView.ItemDecoration {
- private final int horizontalSpacing;
- private final int verticalSpacing;
-
- public SpacingDecoration(int horizontalSpacing, int verticalSpacing) {
- this.horizontalSpacing = horizontalSpacing;
- this.verticalSpacing = verticalSpacing;
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- outRect.set(horizontalSpacing, verticalSpacing, horizontalSpacing, verticalSpacing);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java b/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java
deleted file mode 100644
index b302d3522..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
- * on tablets. See {@code TabMenuStripLayout} for details about how the
- * tabs are created and updated.
- */
-public class TabMenuStrip extends HorizontalScrollView
- implements HomePager.Decor {
-
- // Offset between the selected tab title and the edge of the screen,
- // except for the first and last tab in the tab strip.
- private static final int TITLE_OFFSET_DIPS = 24;
-
- private final int titleOffset;
- private final TabMenuStripLayout layout;
-
- private final Paint shadowPaint;
- private final int shadowSize;
-
- public interface OnTitleClickListener {
- void onTitleClicked(int index);
- }
-
- public TabMenuStrip(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // Disable the scroll bar.
- setHorizontalScrollBarEnabled(false);
- setFillViewport(true);
-
- final Resources res = getResources();
-
- titleOffset = (int) (TITLE_OFFSET_DIPS * res.getDisplayMetrics().density);
-
- layout = new TabMenuStripLayout(context, attrs);
- addView(layout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-
- shadowSize = res.getDimensionPixelSize(R.dimen.tabs_strip_shadow_size);
-
- shadowPaint = new Paint();
- shadowPaint.setColor(ContextCompat.getColor(context, R.color.url_bar_shadow));
- shadowPaint.setStrokeWidth(0.0f);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- final int height = getHeight();
- canvas.drawRect(0, height - shadowSize, layout.getWidth(), height, shadowPaint);
- }
-
- @Override
- public void onAddPagerView(String title) {
- layout.onAddPagerView(title);
- }
-
- @Override
- public void removeAllPagerViews() {
- layout.removeAllViews();
- }
-
- @Override
- public void onPageSelected(final int position) {
- layout.onPageSelected(position);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
-
- final View selectedTitle = layout.getChildAt(position);
- if (selectedTitle == null) {
- return;
- }
-
- final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
-
- int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
- if (position > 0) {
- titleLeft -= titleOffset;
- }
-
- int titleRight = selectedTitle.getRight() + selectedTitleOffset;
- if (position < layout.getChildCount() - 1) {
- titleRight += titleOffset;
- }
-
- final int scrollX = getScrollX();
- if (titleLeft < scrollX) {
- // Tab strip overflows to the left.
- scrollTo(titleLeft, 0);
- } else if (titleRight > scrollX + getWidth()) {
- // Tab strip overflows to the right.
- scrollTo(titleRight - getWidth(), 0);
- }
- }
-
- @Override
- public void setOnTitleClickListener(OnTitleClickListener onTitleClickListener) {
- layout.setOnTitleClickListener(onTitleClickListener);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
deleted file mode 100644
index a09add80b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import android.content.res.ColorStateList;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.TextView;
-
-/**
- * {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
- * tabs that are displayed in {@code TabMenuStrip}.
- */
-class TabMenuStripLayout extends LinearLayout
- implements View.OnFocusChangeListener {
-
- private TabMenuStrip.OnTitleClickListener onTitleClickListener;
- private Drawable strip;
- private TextView selectedView;
-
- // Data associated with the scrolling of the strip drawable.
- private View toTab;
- private View fromTab;
- private int fromPosition;
- private int toPosition;
- private float progress;
-
- // This variable is used to predict the direction of scroll.
- private float prevProgress;
- private int tabContentStart;
- private boolean titlebarFill;
- private int activeTextColor;
- private ColorStateList inactiveTextColor;
-
- TabMenuStripLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
- final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
-
- titlebarFill = a.getBoolean(R.styleable.TabMenuStrip_titlebarFill, false);
- tabContentStart = a.getDimensionPixelSize(R.styleable.TabMenuStrip_tabsMarginLeft, 0);
- activeTextColor = a.getColor(R.styleable.TabMenuStrip_activeTextColor, R.color.text_and_tabs_tray_grey);
- inactiveTextColor = a.getColorStateList(R.styleable.TabMenuStrip_inactiveTextColor);
- a.recycle();
-
- if (stripResId != -1) {
- strip = getResources().getDrawable(stripResId);
- }
-
- setWillNotDraw(false);
- }
-
- void onAddPagerView(String title) {
- final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
- button.setText(title.toUpperCase());
- button.setTextColor(inactiveTextColor);
-
- // Set titles width to weight, or wrap text width.
- if (titlebarFill) {
- button.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f));
- } else {
- button.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
- }
-
- if (getChildCount() == 0) {
- button.setPadding(button.getPaddingLeft() + tabContentStart,
- button.getPaddingTop(),
- button.getPaddingRight(),
- button.getPaddingBottom());
- }
-
- addView(button);
- button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
- button.setOnFocusChangeListener(this);
- }
-
- void onPageSelected(final int position) {
- if (selectedView != null) {
- selectedView.setTextColor(inactiveTextColor);
- }
-
- selectedView = (TextView) getChildAt(position);
- selectedView.setTextColor(activeTextColor);
-
- // Callback to measure and draw the strip after the view is visible.
- ViewTreeObserver vto = selectedView.getViewTreeObserver();
- if (vto.isAlive()) {
- vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-
- if (strip != null) {
- strip.setBounds(selectedView.getLeft() + (position == 0 ? tabContentStart : 0),
- selectedView.getTop(),
- selectedView.getRight(),
- selectedView.getBottom());
- }
-
- prevProgress = position;
- }
- });
- }
- }
-
- // Page scroll animates the drawable and its bounds from the previous to next child view.
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- if (strip == null) {
- return;
- }
-
- setScrollingData(position, positionOffset);
-
- if (fromTab == null || toTab == null) {
- return;
- }
-
- final int fromTabLeft = fromTab.getLeft();
- final int fromTabRight = fromTab.getRight();
-
- final int toTabLeft = toTab.getLeft();
- final int toTabRight = toTab.getRight();
-
- // The first tab has a padding applied (tabContentStart). We don't want the 'strip' to jump around so we remove
- // this padding slowly (modifier) when scrolling to or from the first tab.
- final int modifier;
-
- if (fromPosition == 0 && toPosition == 1) {
- // Slowly remove extra padding (tabContentStart) based on scroll progress
- modifier = (int) (tabContentStart * (1 - progress));
- } else if (fromPosition == 1 && toPosition == 0) {
- // Slowly add extra padding (tabContentStart) based on scroll progress
- modifier = (int) (tabContentStart * progress);
- } else {
- // We are not scrolling tab 0 in any way, no modifier needed
- modifier = 0;
- }
-
- strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)) + modifier,
- 0,
- (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
- getHeight());
- invalidate();
- }
-
- /*
- * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
- * Normalized progress is relative to the the direction the page is being scrolled towards.
- * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
- */
- void setScrollingData(int position, float positionOffset) {
- if (position >= getChildCount() - 1) {
- return;
- }
-
- final float currProgress = position + positionOffset;
-
- if (prevProgress > currProgress) {
- toPosition = position;
- fromPosition = position + 1;
- progress = 1 - positionOffset;
- } else {
- toPosition = position + 1;
- fromPosition = position;
- progress = positionOffset;
- }
-
- toTab = getChildAt(toPosition);
- fromTab = getChildAt(fromPosition);
-
- prevProgress = currProgress;
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (strip != null) {
- strip.draw(canvas);
- }
- }
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (v == this && hasFocus && getChildCount() > 0) {
- selectedView.requestFocus();
- return;
- }
-
- if (!hasFocus) {
- return;
- }
-
- int i = 0;
- final int numTabs = getChildCount();
-
- while (i < numTabs) {
- View view = getChildAt(i);
- if (view == v) {
- view.requestFocus();
- if (isShown()) {
- // A view is focused so send an event to announce the menu strip state.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- break;
- }
-
- i++;
- }
- }
-
- void setOnTitleClickListener(TabMenuStrip.OnTitleClickListener onTitleClickListener) {
- this.onTitleClickListener = onTitleClickListener;
- }
-
- private class ViewClickListener implements OnClickListener {
- private final int mIndex;
-
- public ViewClickListener(int index) {
- mIndex = index;
- }
-
- @Override
- public void onClick(View view) {
- if (onTitleClickListener != null) {
- onTitleClickListener.onTitleClicked(mIndex);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java
deleted file mode 100644
index c17aff209..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.widget.ImageView.ScaleType;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-
-import java.util.concurrent.Future;
-
-/**
- * A view that displays the thumbnail and the title/url for a top/pinned site.
- * If the title/url is longer than the width of the view, they are faded out.
- * If there is no valid url, a default string is shown at 50% opacity.
- * This is denoted by the empty state.
- */
-public class TopSitesGridItemView extends RelativeLayout implements IconCallback {
- private static final String LOGTAG = "GeckoTopSitesGridItemView";
-
- // Empty state, to denote there is no valid url.
- private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
-
- private static final ScaleType SCALE_TYPE_FAVICON = ScaleType.CENTER;
- private static final ScaleType SCALE_TYPE_RESOURCE = ScaleType.CENTER;
- private static final ScaleType SCALE_TYPE_THUMBNAIL = ScaleType.CENTER_CROP;
- private static final ScaleType SCALE_TYPE_URL = ScaleType.CENTER_INSIDE;
-
- // Child views.
- private final TextView mTitleView;
- private final TopSitesThumbnailView mThumbnailView;
-
- // Data backing this view.
- private String mTitle;
- private String mUrl;
-
- private boolean mThumbnailSet;
-
- // Matches BrowserContract.TopSites row types
- private int mType = -1;
-
- // Dirty state.
- private boolean mIsDirty;
-
- private Future<IconResponse> mOngoingIconRequest;
-
- public TopSitesGridItemView(Context context) {
- this(context, null);
- }
-
- public TopSitesGridItemView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesGridItemViewStyle);
- }
-
- public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this);
-
- mTitleView = (TextView) findViewById(R.id.title);
- mThumbnailView = (TopSitesThumbnailView) findViewById(R.id.thumbnail);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (mType == TopSites.TYPE_BLANK) {
- mergeDrawableStates(drawableState, STATE_EMPTY);
- }
-
- return drawableState;
- }
-
- /**
- * @return The title shown by this view.
- */
- public String getTitle() {
- return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl);
- }
-
- /**
- * @return The url shown by this view.
- */
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * @return The site type associated with this view.
- */
- public int getType() {
- return mType;
- }
-
- /**
- * @param title The title for this view.
- */
- public void setTitle(String title) {
- if (mTitle != null && mTitle.equals(title)) {
- return;
- }
-
- mTitle = title;
- updateTitleView();
- }
-
- /**
- * @param url The url for this view.
- */
- public void setUrl(String url) {
- if (mUrl != null && mUrl.equals(url)) {
- return;
- }
-
- mUrl = url;
- updateTitleView();
- }
-
- public void blankOut() {
- mUrl = "";
- mTitle = "";
- updateType(TopSites.TYPE_BLANK);
- updateTitleView();
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
- displayThumbnail(R.drawable.top_site_add);
-
- }
-
- public void markAsDirty() {
- mIsDirty = true;
- }
-
- /**
- * Updates the title, URL, and pinned state of this view.
- *
- * Also resets our loadId to NOT_LOADING.
- *
- * Returns true if any fields changed.
- */
- public boolean updateState(final String title, final String url, final int type, final TopSitesPanel.ThumbnailInfo thumbnail) {
- boolean changed = false;
- if (mUrl == null || !mUrl.equals(url)) {
- mUrl = url;
- changed = true;
- }
-
- if (mTitle == null || !mTitle.equals(title)) {
- mTitle = title;
- changed = true;
- }
-
- if (thumbnail != null) {
- if (thumbnail.imageUrl != null) {
- displayThumbnail(thumbnail.imageUrl, thumbnail.bgColor);
- } else if (thumbnail.bitmap != null) {
- displayThumbnail(thumbnail.bitmap);
- }
- } else if (changed) {
- // Because we'll have a new favicon or thumbnail arriving shortly, and
- // we need to not reject it because we already had a thumbnail.
- mThumbnailSet = false;
- }
-
- if (changed) {
- updateTitleView();
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
- }
-
- if (updateType(type)) {
- changed = true;
- }
-
- // The dirty state forces the state update to return true
- // so that the adapter loads favicons once the thumbnails
- // are loaded in TopSitesPanel/TopSitesGridAdapter.
- changed = (changed || mIsDirty);
- mIsDirty = false;
-
- return changed;
- }
-
- /**
- * Try to load an icon for the given page URL.
- */
- public void loadFavicon(String pageUrl) {
- mOngoingIconRequest = Icons.with(getContext())
- .pageUrl(pageUrl)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- private void cancelIconLoading() {
- if (mOngoingIconRequest != null) {
- mOngoingIconRequest.cancel(true);
- }
- }
-
- /**
- * Display the thumbnail from a resource.
- *
- * @param resId Resource ID of the drawable to show.
- */
- public void displayThumbnail(int resId) {
- mThumbnailView.setScaleType(SCALE_TYPE_RESOURCE);
- mThumbnailView.setImageResource(resId);
- mThumbnailView.setBackgroundColor(0x0);
- mThumbnailSet = false;
- }
-
- /**
- * Display the thumbnail from a bitmap.
- *
- * @param thumbnail The bitmap to show as thumbnail.
- */
- public void displayThumbnail(Bitmap thumbnail) {
- if (thumbnail == null) {
- return;
- }
-
- mThumbnailSet = true;
-
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
-
- mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL);
- mThumbnailView.setImageBitmap(thumbnail, true);
- mThumbnailView.setBackgroundDrawable(null);
- }
-
- /**
- * Display the thumbnail from a URL.
- *
- * @param imageUrl URL of the image to show.
- * @param bgColor background color to use in the view.
- */
- public void displayThumbnail(final String imageUrl, final int bgColor) {
- mThumbnailView.setScaleType(SCALE_TYPE_URL);
- mThumbnailView.setBackgroundColor(bgColor);
- mThumbnailSet = true;
-
- ImageLoader.with(getContext())
- .load(imageUrl)
- .noFade()
- .into(mThumbnailView);
- }
-
- /**
- * Update the item type associated with this view. Returns true if
- * the type has changed, false otherwise.
- */
- private boolean updateType(int type) {
- if (mType == type) {
- return false;
- }
-
- mType = type;
- refreshDrawableState();
-
- int pinResourceId = (type == TopSites.TYPE_PINNED ? R.drawable.pin : 0);
- mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinResourceId, 0, 0, 0);
-
- return true;
- }
-
- /**
- * Update the title shown by this view. If both title and url
- * are empty, mark the state as STATE_EMPTY and show a default text.
- */
- private void updateTitleView() {
- String title = getTitle();
- if (!TextUtils.isEmpty(title)) {
- mTitleView.setText(title);
- } else {
- mTitleView.setText(R.string.home_top_sites_add);
- }
- }
-
- /**
- * Display the loaded icon (if no thumbnail is set).
- */
- @Override
- public void onIconResponse(IconResponse response) {
- if (mThumbnailSet) {
- // Already showing a thumbnail; do nothing.
- return;
- }
-
- mThumbnailView.setScaleType(SCALE_TYPE_FAVICON);
- mThumbnailView.setImageBitmap(response.getBitmap(), false);
- mThumbnailView.setBackgroundColorWithOpacityFilter(response.getColor());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java
deleted file mode 100644
index 58a05b198..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ThumbnailHelper;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AbsListView;
-import android.widget.GridView;
-
-/**
- * A grid view of top and pinned sites.
- * Each cell in the grid is a TopSitesGridItemView.
- */
-public class TopSitesGridView extends GridView {
- private static final String LOGTAG = "GeckoTopSitesGridView";
-
- // Listener for editing pinned sites.
- public static interface OnEditPinnedSiteListener {
- public void onEditPinnedSite(int position, String searchTerm);
- }
-
- // Max number of top sites that needs to be shown.
- private final int mMaxSites;
-
- // Number of columns to show.
- private final int mNumColumns;
-
- // Horizontal spacing in between the rows.
- private final int mHorizontalSpacing;
-
- // Vertical spacing in between the rows.
- private final int mVerticalSpacing;
-
- // Measured width of this view.
- private int mMeasuredWidth;
-
- // Measured height of this view.
- private int mMeasuredHeight;
-
- // A dummy View used to measure the required size of the child Views.
- private final TopSitesGridItemView dummyChildView;
-
- // Context menu info.
- private TopSitesGridContextMenuInfo mContextMenuInfo;
-
- // Whether we're handling focus changes or not. This is used
- // to avoid infinite re-layouts when using this GridView as
- // a ListView header view (see bug 918044).
- private boolean mIsHandlingFocusChange;
-
- public TopSitesGridView(Context context) {
- this(context, null);
- }
-
- public TopSitesGridView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesGridViewStyle);
- }
-
- public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
- mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
- setNumColumns(mNumColumns);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
- mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
- mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
- a.recycle();
-
- dummyChildView = new TopSitesGridItemView(context);
- // Set a default LayoutParams on the child, if it doesn't have one on its own.
- AbsListView.LayoutParams params = (AbsListView.LayoutParams) dummyChildView.getLayoutParams();
- if (params == null) {
- params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
- AbsListView.LayoutParams.WRAP_CONTENT);
- dummyChildView.setLayoutParams(params);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- mIsHandlingFocusChange = true;
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- mIsHandlingFocusChange = false;
- }
-
- @Override
- public void requestLayout() {
- if (!mIsHandlingFocusChange) {
- super.requestLayout();
- }
- }
-
- @Override
- public int getColumnWidth() {
- // This method will be called from onMeasure() too.
- // It's better to use getMeasuredWidth(), as it is safe in this case.
- final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0;
- return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Sets the padding for this view.
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int measuredWidth = getMeasuredWidth();
- if (measuredWidth == mMeasuredWidth) {
- // Return the cached values as the width is the same.
- setMeasuredDimension(mMeasuredWidth, mMeasuredHeight);
- return;
- }
-
- final int columnWidth = getColumnWidth();
-
- // Measure the exact width of the child, and the height based on the width.
- // Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
- int childWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
- int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- dummyChildView.measure(childWidthSpec, childHeightSpec);
- final int childHeight = dummyChildView.getMeasuredHeight();
-
- // This is the maximum width of the contents of each child in the grid.
- // Use this as the target width for thumbnails.
- final int thumbnailWidth = dummyChildView.getMeasuredWidth() - dummyChildView.getPaddingLeft() - dummyChildView.getPaddingRight();
- ThumbnailHelper.getInstance().setThumbnailWidth(thumbnailWidth);
-
- // Number of rows required to show these top sites.
- final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
- final int childrenHeight = childHeight * rows;
- final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
-
- // Total height of this view.
- final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
- setMeasuredDimension(measuredWidth, measuredHeight);
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- public void setContextMenuInfo(TopSitesGridContextMenuInfo contextMenuInfo) {
- mContextMenuInfo = contextMenuInfo;
- }
-
- /**
- * Stores information regarding the creation of the context menu for a GridView item.
- */
- public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
- public int type = -1;
-
- public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
- super(targetView, position, id);
- this.itemType = RemoveItemType.HISTORY;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
deleted file mode 100644
index f39e51ac5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
+++ /dev/null
@@ -1,968 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_COLOR_COLUMN;
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_IMAGE_URL_COLUMN;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Future;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract.Thumbnails;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
-import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
-import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.AsyncTaskLoader;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListView;
-
-/**
- * Fragment that displays frecency search results in a ListView.
- */
-public class TopSitesPanel extends HomeFragment {
- // Logging tag name
- private static final String LOGTAG = "GeckoTopSitesPanel";
-
- // Cursor loader ID for the top sites
- private static final int LOADER_ID_TOP_SITES = 0;
-
- // Loader ID for thumbnails
- private static final int LOADER_ID_THUMBNAILS = 1;
-
- // Key for thumbnail urls
- private static final String THUMBNAILS_URLS_KEY = "urls";
-
- // Adapter for the list of top sites
- private VisitedAdapter mListAdapter;
-
- // Adapter for the grid of top sites
- private TopSitesGridAdapter mGridAdapter;
-
- // List of top sites
- private HomeListView mList;
-
- // Grid of top sites
- private TopSitesGridView mGrid;
-
- // Callbacks used for the search and favicon cursor loaders
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- // Callback for thumbnail loader
- private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
-
- // Listener for editing pinned sites.
- private EditPinnedSiteListener mEditPinnedSiteListener;
-
- // Max number of entries shown in the grid from the cursor.
- private int mMaxGridEntries;
-
- // Time in ms until the Gecko thread is reset to normal priority.
- private static final long PRIORITY_RESET_TIMEOUT = 10000;
-
- public static TopSitesPanel newInstance() {
- return new TopSitesPanel();
- }
-
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
-
- private static void debug(final String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-
- private static void trace(final String message) {
- if (logVerbose) {
- Log.v(LOGTAG, message);
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.home_top_sites_panel, container, false);
-
- mList = (HomeListView) view.findViewById(R.id.list);
-
- mGrid = new TopSitesGridView(getActivity());
- mList.addHeaderView(mGrid);
-
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- mEditPinnedSiteListener = new EditPinnedSiteListener();
-
- mList.setTag(HomePager.LIST_TAG_TOP_SITES);
- mList.setHeaderDividersEnabled(false);
-
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final ListView list = (ListView) parent;
- final int headerCount = list.getHeaderViewsCount();
- if (position < headerCount) {
- // The click is on a header, don't do anything.
- return;
- }
-
- // Absolute position for the adapter.
- position += (mGridAdapter.getCount() - headerCount);
-
- final Cursor c = mListAdapter.getCursor();
- if (c == null || !c.moveToPosition(position)) {
- return;
- }
-
- final String url = c.getString(c.getColumnIndexOrThrow(TopSites.URL));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "top_sites");
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- });
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
- info.itemType = RemoveItemType.HISTORY;
- final int bookmarkIdCol = cursor.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
- if (cursor.isNull(bookmarkIdCol)) {
- // If this is a combined cursor, we may get a history item without a
- // bookmark, in which case the bookmarks ID column value will be null.
- info.bookmarkId = -1;
- } else {
- info.bookmarkId = cursor.getInt(bookmarkIdCol);
- }
- return info;
- }
- });
-
- mGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- TopSitesGridItemView item = (TopSitesGridItemView) view;
-
- // Decode "user-entered" URLs before loading them.
- String url = StringUtils.decodeUserEnteredUrl(item.getUrl());
- int type = item.getType();
-
- // If the url is empty, the user can pin a site.
- // If not, navigate to the page given by the url.
- if (type != TopSites.TYPE_BLANK) {
- if (mUrlOpenListener != null) {
- final TelemetryContract.Method method;
- if (type == TopSites.TYPE_SUGGESTED) {
- method = TelemetryContract.Method.SUGGESTION;
- } else {
- method = TelemetryContract.Method.GRID_ITEM;
- }
-
- String extra = Integer.toString(position);
- if (type == TopSites.TYPE_PINNED) {
- extra += "-pinned";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method, extra);
-
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.NO_READER_VIEW));
- }
- } else {
- if (mEditPinnedSiteListener != null) {
- mEditPinnedSiteListener.onEditPinnedSite(position, "");
- }
- }
- }
- });
-
- mGrid.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-
- Cursor cursor = (Cursor) parent.getItemAtPosition(position);
-
- TopSitesGridItemView item = (TopSitesGridItemView) view;
- if (cursor == null || item.getType() == TopSites.TYPE_BLANK) {
- mGrid.setContextMenuInfo(null);
- return false;
- }
-
- TopSitesGridContextMenuInfo contextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id);
- updateContextMenuFromCursor(contextMenuInfo, cursor);
- mGrid.setContextMenuInfo(contextMenuInfo);
- return mGrid.showContextMenuForChild(mGrid);
- }
-
- /*
- * Update the fields of a TopSitesGridContextMenuInfo object
- * from a cursor.
- *
- * @param info context menu info object to be updated
- * @param cursor used to update the context menu info object
- */
- private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- info.type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
- }
- });
-
- registerForContextMenu(mList);
- registerForContextMenu(mGrid);
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- // Discard any additional item clicks on the list as the
- // panel is getting destroyed (see bugs 930160 & 1096958).
- mList.setOnItemClickListener(null);
- mGrid.setOnItemClickListener(null);
-
- mList = null;
- mGrid = null;
- mListAdapter = null;
- mGridAdapter = null;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Activity activity = getActivity();
-
- // Setup the top sites grid adapter.
- mGridAdapter = new TopSitesGridAdapter(activity, null);
- mGrid.setAdapter(mGridAdapter);
-
- // Setup the top sites list adapter.
- mListAdapter = new VisitedAdapter(activity, null);
- mList.setAdapter(mListAdapter);
-
- // Create callbacks before the initial loader is started
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (menuInfo == null) {
- return;
- }
-
- if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
- // Long pressed item was not a Top Sites GridView item. Superclass
- // can handle this.
- super.onCreateContextMenu(menu, view, menuInfo);
-
- if (!Restrictions.isAllowed(view.getContext(), Restrictable.CLEAR_HISTORY)) {
- menu.findItem(R.id.home_remove).setVisible(false);
- }
-
- return;
- }
-
- final Context context = view.getContext();
-
- // Long pressed item was a Top Sites GridView item, handle it.
- MenuInflater inflater = new MenuInflater(context);
- inflater.inflate(R.menu.home_contextmenu, menu);
-
- // Hide unused menu items.
- menu.findItem(R.id.home_edit_bookmark).setVisible(false);
-
- menu.findItem(R.id.home_remove).setVisible(Restrictions.isAllowed(context, Restrictable.CLEAR_HISTORY));
-
- TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
- menu.setHeaderTitle(info.getDisplayTitle());
-
- if (info.type != TopSites.TYPE_BLANK) {
- if (info.type == TopSites.TYPE_PINNED) {
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- } else {
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
- }
- } else {
- menu.findItem(R.id.home_open_new_tab).setVisible(false);
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
- }
-
- if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
- menu.findItem(R.id.home_share).setVisible(false);
- }
-
- if (!Restrictions.isAllowed(context, Restrictable.PRIVATE_BROWSING)) {
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (super.onContextItemSelected(item)) {
- // HomeFragment was able to handle to selected item.
- return true;
- }
-
- ContextMenuInfo menuInfo = item.getMenuInfo();
-
- if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
- return false;
- }
-
- TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
-
- final int itemId = item.getItemId();
- final BrowserDB db = BrowserDB.from(getActivity());
-
- if (itemId == R.id.top_sites_pin) {
- final String url = info.url;
- final String title = info.title;
- final int position = info.position;
- final Context context = getActivity().getApplicationContext();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.pinSite(context.getContentResolver(), url, title, position);
- }
- });
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PIN);
- return true;
- }
-
- if (itemId == R.id.top_sites_unpin) {
- final int position = info.position;
- final Context context = getActivity().getApplicationContext();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.unpinSite(context.getContentResolver(), position);
- }
- });
-
- Telemetry.sendUIEvent(TelemetryContract.Event.UNPIN);
-
- return true;
- }
-
- if (itemId == R.id.top_sites_edit) {
- // Decode "user-entered" URLs before showing them.
- mEditPinnedSiteListener.onEditPinnedSite(info.position,
- StringUtils.decodeUserEnteredUrl(info.url));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.EDIT);
- return true;
- }
-
- return false;
- }
-
- @Override
- protected void load() {
- getLoaderManager().initLoader(LOADER_ID_TOP_SITES, null, mCursorLoaderCallbacks);
-
- // Since this is the primary fragment that loads whenever about:home is
- // visited, we want to load it as quickly as possible. Heavy load on
- // the Gecko thread can slow down the time it takes for thumbnails to
- // appear, especially during startup (bug 897162). By minimizing the
- // Gecko thread priority, we ensure that the UI appears quickly. The
- // priority is reset to normal once thumbnails are loaded.
- ThreadUtils.reduceGeckoPriority(PRIORITY_RESET_TIMEOUT);
- }
-
- /**
- * Listener for editing pinned sites.
- */
- private class EditPinnedSiteListener implements OnEditPinnedSiteListener,
- OnSiteSelectedListener {
- // Tag for the PinSiteDialog fragment.
- private static final String TAG_PIN_SITE = "pin_site";
-
- // Position of the pin.
- private int mPosition;
-
- @Override
- public void onEditPinnedSite(int position, String searchTerm) {
- final FragmentManager manager = getChildFragmentManager();
- PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE);
- if (dialog == null) {
- mPosition = position;
-
- dialog = PinSiteDialog.newInstance();
- dialog.setOnSiteSelectedListener(this);
- dialog.setSearchTerm(searchTerm);
- dialog.show(manager, TAG_PIN_SITE);
- }
- }
-
- @Override
- public void onSiteSelected(final String url, final String title) {
- final int position = mPosition;
- final Context context = getActivity().getApplicationContext();
- final BrowserDB db = BrowserDB.from(getActivity());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.pinSite(context.getContentResolver(), url, title, position);
- }
- });
- }
- }
-
- private void updateUiFromCursor(Cursor c) {
- mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
- }
-
- private void updateUiWithThumbnails(Map<String, ThumbnailInfo> thumbnails) {
- if (mGridAdapter != null) {
- mGridAdapter.updateThumbnails(thumbnails);
- }
-
- // Once thumbnails have finished loading, the UI is ready. Reset
- // Gecko to normal priority.
- ThreadUtils.resetGeckoPriority();
- }
-
- private static class TopSitesLoader extends SimpleCursorLoader {
- // Max number of search results.
- private static final int SEARCH_LIMIT = 30;
- private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_TOPSITES_LOADER_TIME_MS";
- private final BrowserDB mDB;
- private final int mMaxGridEntries;
-
- public TopSitesLoader(Context context) {
- super(context);
- mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final long start = SystemClock.uptimeMillis();
- final Cursor cursor = mDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
- return cursor;
- }
- }
-
- private class VisitedAdapter extends CursorAdapter {
- public VisitedAdapter(Context context, Cursor cursor) {
- super(context, cursor, 0);
- }
-
- @Override
- public int getCount() {
- return Math.max(0, super.getCount() - mMaxGridEntries);
- }
-
- @Override
- public Object getItem(int position) {
- return super.getItem(position + mMaxGridEntries);
- }
-
- /**
- * We have to override default getItemId implementation, since for a given position, it returns
- * value of the _id column. In our case _id is always 0 (see Combined view).
- */
- @Override
- public long getItemId(int position) {
- final int adjustedPosition = position + mMaxGridEntries;
- final Cursor cursor = getCursor();
-
- cursor.moveToPosition(adjustedPosition);
- return getItemIdForTopSitesCursor(cursor);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- final int position = cursor.getPosition();
- cursor.moveToPosition(position + mMaxGridEntries);
-
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(cursor);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context).inflate(R.layout.bookmark_item_row, parent, false);
- }
- }
-
- public class TopSitesGridAdapter extends CursorAdapter {
- private final BrowserDB mDB;
- // Cache to store the thumbnails.
- // Ensure that this is only accessed from the UI thread.
- private Map<String, ThumbnailInfo> mThumbnailInfos;
-
- public TopSitesGridAdapter(Context context, Cursor cursor) {
- super(context, cursor, 0);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public int getCount() {
- return Math.min(mMaxGridEntries, super.getCount());
- }
-
- @Override
- protected void onContentChanged() {
- // Don't do anything. We don't want to regenerate every time
- // our database is updated.
- return;
- }
-
- /**
- * Update the thumbnails returned by the db.
- *
- * @param thumbnails A map of urls and their thumbnail bitmaps.
- */
- public void updateThumbnails(Map<String, ThumbnailInfo> thumbnails) {
- mThumbnailInfos = thumbnails;
-
- final int count = mGrid.getChildCount();
- for (int i = 0; i < count; i++) {
- TopSitesGridItemView gridItem = (TopSitesGridItemView) mGrid.getChildAt(i);
-
- // All the views have already got their initial state at this point.
- // This will force each view to load favicons for the missing
- // thumbnails if necessary.
- gridItem.markAsDirty();
- }
-
- notifyDataSetChanged();
- }
-
- /**
- * We have to override default getItemId implementation, since for a given position, it returns
- * value of the _id column. In our case _id is always 0 (see Combined view).
- */
- @Override
- public long getItemId(int position) {
- final Cursor cursor = getCursor();
- cursor.moveToPosition(position);
-
- return getItemIdForTopSitesCursor(cursor);
- }
-
- @Override
- public void bindView(View bindView, Context context, Cursor cursor) {
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- final String title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- final int type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
-
- final TopSitesGridItemView view = (TopSitesGridItemView) bindView;
-
- // If there is no url, then show "add bookmark".
- if (type == TopSites.TYPE_BLANK) {
- view.blankOut();
- return;
- }
-
- // Show the thumbnail, if any.
- ThumbnailInfo thumbnail = (mThumbnailInfos != null ? mThumbnailInfos.get(url) : null);
-
- // Debounce bindView calls to avoid redundant redraws and favicon
- // fetches.
- final boolean updated = view.updateState(title, url, type, thumbnail);
-
- // Thumbnails are delivered late, so we can't short-circuit any
- // sooner than this. But we can avoid a duplicate favicon
- // fetch...
- if (!updated) {
- debug("bindView called twice for same values; short-circuiting.");
- return;
- }
-
- // Make sure we query suggested images without the user-entered wrapper.
- final String decodedUrl = StringUtils.decodeUserEnteredUrl(url);
-
- // Suggested images have precedence over thumbnails, no need to wait
- // for them to be loaded. See: CursorLoaderCallbacks.onLoadFinished()
- final String imageUrl = mDB.getSuggestedImageUrlForUrl(decodedUrl);
- if (!TextUtils.isEmpty(imageUrl)) {
- final int bgColor = mDB.getSuggestedBackgroundColorForUrl(decodedUrl);
- view.displayThumbnail(imageUrl, bgColor);
- return;
- }
-
- // If thumbnails are still being loaded, don't try to load favicons
- // just yet. If we sent in a thumbnail, we're done now.
- if (mThumbnailInfos == null || thumbnail != null) {
- return;
- }
-
- view.loadFavicon(url);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return new TopSitesGridItemView(context);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- trace("Creating TopSitesLoader: " + id);
- return new TopSitesLoader(getActivity());
- }
-
- /**
- * This method is called *twice* in some circumstances.
- *
- * If you try to avoid that through some kind of boolean flag,
- * sometimes (e.g., returning to the activity) you'll *not* be called
- * twice, and thus you'll never draw thumbnails.
- *
- * The root cause is TopSitesLoader.loadCursor being called twice.
- * Why that is... dunno.
- */
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- debug("onLoadFinished: " + c.getCount() + " rows.");
-
- mListAdapter.swapCursor(c);
- mGridAdapter.swapCursor(c);
- updateUiFromCursor(c);
-
- final int col = c.getColumnIndexOrThrow(TopSites.URL);
-
- // Load the thumbnails.
- // Even though the cursor we're given is supposed to be fresh,
- // we getIcon a bad first value unless we reset its position.
- // Using move(-1) and moveToNext() doesn't work correctly under
- // rotation, so we use moveToFirst.
- if (!c.moveToFirst()) {
- return;
- }
-
- final ArrayList<String> urls = new ArrayList<String>();
- int i = 1;
- do {
- final String url = c.getString(col);
-
- // Only try to fetch thumbnails for non-empty URLs that
- // don't have an associated suggested image URL.
- final GeckoProfile profile = GeckoProfile.get(getActivity());
- if (TextUtils.isEmpty(url) || BrowserDB.from(profile).hasSuggestedImageUrl(url)) {
- continue;
- }
-
- urls.add(url);
- } while (i++ < mMaxGridEntries && c.moveToNext());
-
- if (urls.isEmpty()) {
- // Short-circuit empty results to the UI.
- updateUiWithThumbnails(new HashMap<String, ThumbnailInfo>());
- return;
- }
-
- Bundle bundle = new Bundle();
- bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
- getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mListAdapter != null) {
- mListAdapter.swapCursor(null);
- }
-
- if (mGridAdapter != null) {
- mGridAdapter.swapCursor(null);
- }
- }
- }
-
- static class ThumbnailInfo {
- public final Bitmap bitmap;
- public final String imageUrl;
- public final int bgColor;
-
- public ThumbnailInfo(final Bitmap bitmap) {
- this.bitmap = bitmap;
- this.imageUrl = null;
- this.bgColor = Color.TRANSPARENT;
- }
-
- public ThumbnailInfo(final String imageUrl, final int bgColor) {
- this.bitmap = null;
- this.imageUrl = imageUrl;
- this.bgColor = bgColor;
- }
-
- public static ThumbnailInfo fromMetadata(final Map<String, Object> data) {
- if (data == null) {
- return null;
- }
-
- final String imageUrl = (String) data.get(TILE_IMAGE_URL_COLUMN);
- if (imageUrl == null) {
- return null;
- }
-
- int bgColor = Color.WHITE;
- final String colorString = (String) data.get(TILE_COLOR_COLUMN);
- try {
- bgColor = Color.parseColor(colorString);
- } catch (Exception ex) {
- }
-
- return new ThumbnailInfo(imageUrl, bgColor);
- }
- }
-
- /**
- * An AsyncTaskLoader to load the thumbnails from a cursor.
- */
- static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, ThumbnailInfo>> {
- private final BrowserDB mDB;
- private Map<String, ThumbnailInfo> mThumbnailInfos;
- private final ArrayList<String> mUrls;
-
- private static final List<String> COLUMNS;
- static {
- final ArrayList<String> tempColumns = new ArrayList<>(2);
- tempColumns.add(TILE_IMAGE_URL_COLUMN);
- tempColumns.add(TILE_COLOR_COLUMN);
- COLUMNS = Collections.unmodifiableList(tempColumns);
- }
-
- public ThumbnailsLoader(Context context, ArrayList<String> urls) {
- super(context);
- mUrls = urls;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Map<String, ThumbnailInfo> loadInBackground() {
- final Map<String, ThumbnailInfo> thumbnails = new HashMap<String, ThumbnailInfo>();
- if (mUrls == null || mUrls.size() == 0) {
- return thumbnails;
- }
-
- // We need to query metadata based on the URL without any refs, hence we create a new
- // mapping and list of these URLs (we need to preserve the original URL for display purposes)
- final Map<String, String> queryURLs = new HashMap<>();
- for (final String pageURL : mUrls) {
- queryURLs.put(pageURL, StringUtils.stripRef(pageURL));
- }
-
- // Query the DB for tile images.
- final ContentResolver cr = getContext().getContentResolver();
- // Use the stripped URLs for querying the DB
- final Map<String, Map<String, Object>> metadata = mDB.getURLMetadata().getForURLs(cr, queryURLs.values(), COLUMNS);
-
- // Keep a list of urls that don't have tiles images. We'll use thumbnails for them instead.
- final List<String> thumbnailUrls = new ArrayList<String>();
- for (final String pageURL : mUrls) {
- final String queryURL = queryURLs.get(pageURL);
-
- ThumbnailInfo info = ThumbnailInfo.fromMetadata(metadata.get(queryURL));
- if (info == null) {
- // If we didn't find metadata, we'll look for a thumbnail for this url.
- thumbnailUrls.add(pageURL);
- continue;
- }
-
- thumbnails.put(pageURL, info);
- }
-
- if (thumbnailUrls.size() == 0) {
- return thumbnails;
- }
-
- // Query the DB for tile thumbnails.
- final Cursor cursor = mDB.getThumbnailsForUrls(cr, thumbnailUrls);
- if (cursor == null) {
- return thumbnails;
- }
-
- try {
- final int urlIndex = cursor.getColumnIndexOrThrow(Thumbnails.URL);
- final int dataIndex = cursor.getColumnIndexOrThrow(Thumbnails.DATA);
-
- while (cursor.moveToNext()) {
- String url = cursor.getString(urlIndex);
-
- // This should never be null, but if it is...
- final byte[] b = cursor.getBlob(dataIndex);
- if (b == null) {
- continue;
- }
-
- final Bitmap bitmap = BitmapUtils.decodeByteArray(b);
-
- // Our thumbnails are never null, so if we getIcon a null decoded
- // bitmap, it's because we hit an OOM or some other disaster.
- // Give up immediately rather than hammering on.
- if (bitmap == null) {
- Log.w(LOGTAG, "Aborting thumbnail load; decode failed.");
- break;
- }
-
- thumbnails.put(url, new ThumbnailInfo(bitmap));
- }
- } finally {
- cursor.close();
- }
-
- return thumbnails;
- }
-
- @Override
- public void deliverResult(Map<String, ThumbnailInfo> thumbnails) {
- if (isReset()) {
- mThumbnailInfos = null;
- return;
- }
-
- mThumbnailInfos = thumbnails;
-
- if (isStarted()) {
- super.deliverResult(thumbnails);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mThumbnailInfos != null) {
- deliverResult(mThumbnailInfos);
- }
-
- if (takeContentChanged() || mThumbnailInfos == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- public void onCanceled(Map<String, ThumbnailInfo> thumbnails) {
- mThumbnailInfos = null;
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped.
- onStopLoading();
-
- mThumbnailInfos = null;
- }
- }
-
- /**
- * Loader callbacks for the thumbnails on TopSitesGridView.
- */
- private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, ThumbnailInfo>> {
- @Override
- public Loader<Map<String, ThumbnailInfo>> onCreateLoader(int id, Bundle args) {
- return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
- }
-
- @Override
- public void onLoadFinished(Loader<Map<String, ThumbnailInfo>> loader, Map<String, ThumbnailInfo> thumbnails) {
- updateUiWithThumbnails(thumbnails);
- }
-
- @Override
- public void onLoaderReset(Loader<Map<String, ThumbnailInfo>> loader) {
- if (mGridAdapter != null) {
- mGridAdapter.updateThumbnails(null);
- }
- }
- }
-
- /**
- * We are trying to return stable IDs so that Android can recycle views appropriately:
- * - If we have a history ID then we return it
- * - If we only have a bookmark ID then we negate it and return it. We negate it in order
- * to avoid clashing/conflicting with history IDs.
- *
- * @param cursorInPosition Cursor already moved to position for which we're getting a stable ID
- * @return Stable ID for a given cursor
- */
- private static long getItemIdForTopSitesCursor(final Cursor cursorInPosition) {
- final int historyIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.HISTORY_ID);
- final long historyId = cursorInPosition.getLong(historyIdCol);
- if (historyId != 0) {
- return historyId;
- }
-
- final int bookmarkIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
- final long bookmarkId = cursorInPosition.getLong(bookmarkIdCol);
- return -1 * bookmarkId;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java
deleted file mode 100644
index dd45014b0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ThumbnailHelper;
-import org.mozilla.gecko.widget.CropImageView;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-/**
- * A width constrained ImageView to show thumbnails of top and pinned sites.
- */
-public class TopSitesThumbnailView extends CropImageView {
- private static final String LOGTAG = "GeckoTopSitesThumbnailView";
-
- // 27.34% opacity filter for the dominant color.
- private static final int COLOR_FILTER = 0x46FFFFFF;
-
- // Default filter color for "Add a bookmark" views.
- private final int mDefaultColor = ContextCompat.getColor(getContext(), R.color.top_site_default);
-
- // Stroke width for the border.
- private final float mStrokeWidth = getResources().getDisplayMetrics().density * 2;
-
- // Paint for drawing the border.
- private final Paint mBorderPaint;
-
- public TopSitesThumbnailView(Context context) {
- this(context, null);
-
- // A border will be drawn if needed.
- setWillNotDraw(false);
- }
-
- public TopSitesThumbnailView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesThumbnailViewStyle);
- }
-
- public TopSitesThumbnailView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- // Initialize the border paint.
- final Resources res = getResources();
- mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBorderPaint.setColor(ContextCompat.getColor(context, R.color.top_site_border));
- mBorderPaint.setStyle(Paint.Style.STROKE);
- }
-
- @Override
- protected float getAspectRatio() {
- return ThumbnailHelper.TOP_SITES_THUMBNAIL_ASPECT_RATIO;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (getBackground() == null) {
- mBorderPaint.setStrokeWidth(mStrokeWidth);
- canvas.drawRect(0, 0, getWidth(), getHeight(), mBorderPaint);
- }
- }
-
- /**
- * Sets the background color with a filter to reduce the color opacity.
- *
- * @param color the color filter to apply over the drawable.
- */
- public void setBackgroundColorWithOpacityFilter(int color) {
- setBackgroundColor(color & COLOR_FILTER);
- }
-
- /**
- * Sets the background to a Drawable by applying the specified color as a filter.
- *
- * @param color the color filter to apply over the drawable.
- */
- @Override
- public void setBackgroundColor(int color) {
- if (color == 0) {
- color = mDefaultColor;
- }
-
- Drawable drawable = getResources().getDrawable(R.drawable.top_sites_thumbnail_bg);
- drawable.setColorFilter(color, Mode.SRC_ATOP);
- setBackgroundDrawable(drawable);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
deleted file mode 100644
index 68eb8daa5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.lang.ref.WeakReference;
-import java.util.concurrent.Future;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.widget.FaviconView;
-
-public class TwoLinePageRow extends LinearLayout
- implements Tabs.OnTabsChangedListener {
-
- protected static final int NO_ICON = 0;
-
- private final TextView mTitle;
- private final TextView mUrl;
- private final ImageView mStatusIcon;
-
- private int mSwitchToTabIconId;
-
- private final FaviconView mFavicon;
- private Future<IconResponse> mOngoingIconLoad;
-
- private boolean mShowIcons;
-
- // The URL for the page corresponding to this view.
- private String mPageUrl;
-
- private boolean mHasReaderCacheItem;
-
- public TwoLinePageRow(Context context) {
- this(context, null);
- }
-
- public TwoLinePageRow(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setGravity(Gravity.CENTER_VERTICAL);
-
- LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this);
- // Merge layouts lose their padding, so set it dynamically.
- setPadding(0, 0, (int) getResources().getDimension(R.dimen.page_row_edge_padding), 0);
-
- mTitle = (TextView) findViewById(R.id.title);
- mUrl = (TextView) findViewById(R.id.url);
- mStatusIcon = (ImageView) findViewById(R.id.status_icon_bookmark);
-
- mSwitchToTabIconId = NO_ICON;
- mShowIcons = true;
-
- mFavicon = (FaviconView) findViewById(R.id.icon);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // Tabs' listener array is safe to modify during use: its
- // iteration pattern is based on snapshots.
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- /**
- * Update the row in response to a tab change event.
- * <p>
- * This method is always invoked on the UI thread.
- */
- @Override
- public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
- // Carefully check if this tab event is relevant to this row.
- final String pageUrl = mPageUrl;
- if (pageUrl == null) {
- return;
- }
- if (tab == null) {
- return;
- }
-
- // Return early if the page URL doesn't match the current tab URL,
- // or the old tab URL.
- // data is an empty String for ADDED/CLOSED, and contains the previous/old URL during
- // LOCATION_CHANGE (the new URL is retrieved using tab.getURL()).
- // tabURL and data may be about:reader URLs if the current or old tab page was a reader view
- // page, however pageUrl will always be a plain URL (i.e. we only add about:reader when opening
- // a reader view bookmark, at all other times it's a normal bookmark with normal URL).
- final String tabUrl = tab.getURL();
- if (!pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(tabUrl)) &&
- !pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(data))) {
- return;
- }
-
- // Note: we *might* need to update the display status (i.e. switch-to-tab icon/label) if
- // a matching tab has been opened/closed/switched to a different page. updateDisplayedUrl() will
- // determine the changes (if any) that actually need to be made. A tab change with a matching URL
- // does not imply that any changes are needed - e.g. if a given URL is already open in one tab, and
- // is also opened in a second tab, the switch-to-tab status doesn't change, closing 1 of 2 tabs with a URL
- // similarly doesn't change the switch-to-tab display, etc. (However closing the last tab for
- // a given URL does require a status change, as does opening the first tab with that URL.)
- switch (msg) {
- case ADDED:
- case CLOSED:
- case LOCATION_CHANGE:
- updateDisplayedUrl();
- break;
- default:
- break;
- }
- }
-
- private void setTitle(String text) {
- mTitle.setText(text);
- }
-
- protected void setUrl(String text) {
- mUrl.setText(text);
- }
-
- protected void setUrl(int stringId) {
- mUrl.setText(stringId);
- }
-
- protected String getUrl() {
- return mPageUrl;
- }
-
- protected void setSwitchToTabIcon(int iconId) {
- if (mSwitchToTabIconId == iconId) {
- return;
- }
-
- mSwitchToTabIconId = iconId;
- mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, 0, 0);
- }
-
- private void updateStatusIcon(boolean isBookmark, boolean isReaderItem) {
- if (isReaderItem) {
- mStatusIcon.setImageResource(R.drawable.status_icon_readercache);
- } else if (isBookmark) {
- mStatusIcon.setImageResource(R.drawable.star_blue);
- }
-
- if (mShowIcons && (isBookmark || isReaderItem)) {
- mStatusIcon.setVisibility(View.VISIBLE);
- } else if (mShowIcons) {
- // We use INVISIBLE to have consistent padding for our items. This means text/URLs
- // fade consistently in the same location, regardless of them being bookmarked.
- mStatusIcon.setVisibility(View.INVISIBLE);
- } else {
- mStatusIcon.setVisibility(View.GONE);
- }
-
- }
-
- /**
- * Stores the page URL, so that we can use it to replace "Switch to tab" if the open
- * tab changes or is closed.
- */
- private void updateDisplayedUrl(String url, boolean hasReaderCacheItem) {
- mPageUrl = url;
- mHasReaderCacheItem = hasReaderCacheItem;
- updateDisplayedUrl();
- }
-
- /**
- * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
- * Only looks for tabs that are either private or non-private, depending on the current
- * selected tab.
- */
- protected void updateDisplayedUrl() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (selectedTab != null) && (selectedTab.isPrivate());
-
- // We always want to display the underlying page url, however for readermode pages
- // we navigate to the about:reader equivalent, hence we need to use that url when finding
- // existing tabs
- final String navigationUrl = mHasReaderCacheItem ? ReaderModeUtils.getAboutReaderForUrl(mPageUrl) : mPageUrl;
- Tab tab = Tabs.getInstance().getFirstTabForUrl(navigationUrl, isPrivate);
-
-
- if (!mShowIcons || tab == null) {
- setUrl(mPageUrl);
- setSwitchToTabIcon(NO_ICON);
- } else {
- setUrl(R.string.switch_to_tab);
- setSwitchToTabIcon(R.drawable.ic_url_bar_tab);
- }
- }
-
- public void setShowIcons(boolean showIcons) {
- mShowIcons = showIcons;
- }
-
- /**
- * Update the data displayed by this row.
- * <p>
- * This method must be invoked on the UI thread.
- *
- * @param title to display.
- * @param url to display.
- */
- public void update(String title, String url) {
- update(title, url, 0, false);
- }
-
- protected void update(String title, String url, long bookmarkId, boolean hasReaderCacheItem) {
- if (mShowIcons) {
- // The bookmark id will be 0 (null in database) when the url
- // is not a bookmark and negative for 'fake' bookmarks.
- final boolean isBookmark = bookmarkId > 0;
-
- updateStatusIcon(isBookmark, hasReaderCacheItem);
- } else {
- updateStatusIcon(false, false);
- }
-
- // Use the URL instead of an empty title for consistency with the normal URL
- // bar view - this is the equivalent of getDisplayTitle() in Tab.java
- setTitle(TextUtils.isEmpty(title) ? url : title);
-
- // No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
- if (url.equals(mPageUrl)) {
- return;
- }
-
- // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
- mFavicon.clearImage();
-
- if (mOngoingIconLoad != null) {
- mOngoingIconLoad.cancel(true);
- }
-
- // Displayed RecentTabsPanel URLs may refer to pages opened in reader mode, so we
- // remove the about:reader prefix to ensure the Favicon loads properly.
- final String pageURL = ReaderModeUtils.stripAboutReaderUrl(url);
-
- if (TextUtils.isEmpty(pageURL)) {
- // If url is empty, display the item as-is but do not load an icon if we do not have a page URL (bug 1310622)
- } else if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
- mOngoingIconLoad = Icons.with(getContext())
- .pageUrl(pageURL)
- .skipNetwork()
- .privileged(true)
- .icon(IconDescriptor.createGenericIcon(
- PartnerBookmarksProviderProxy.getUriForIcon(getContext(), bookmarkId).toString()))
- .build()
- .execute(mFavicon.createIconCallback());
- } else {
- mOngoingIconLoad = Icons.with(getContext())
- .pageUrl(pageURL)
- .skipNetwork()
- .build()
- .execute(mFavicon.createIconCallback());
-
- }
-
- updateDisplayedUrl(url, hasReaderCacheItem);
- }
-
- /**
- * Update the data displayed by this row.
- * <p>
- * This method must be invoked on the UI thread.
- *
- * @param cursor to extract data from.
- */
- public void updateFromCursor(Cursor cursor) {
- if (cursor == null) {
- return;
- }
-
- int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE);
- final String title = cursor.getString(titleIndex);
-
- int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
- final String url = cursor.getString(urlIndex);
-
- final long bookmarkId;
- final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
- if (bookmarkIdIndex != -1) {
- bookmarkId = cursor.getLong(bookmarkIdIndex);
- } else {
- bookmarkId = 0;
- }
-
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
- final boolean hasReaderCacheItem = rch.isURLCached(url);
-
- update(title, url, bookmarkId, hasReaderCacheItem);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
deleted file mode 100644
index ef0c105d3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.content.Loader;
-import android.support.v4.graphics.ColorUtils;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.widget.FrameLayout;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-public class ActivityStream extends FrameLayout {
- private final StreamRecyclerAdapter adapter;
-
- private static final int LOADER_ID_HIGHLIGHTS = 0;
- private static final int LOADER_ID_TOPSITES = 1;
-
- private static final int MINIMUM_TILES = 4;
- private static final int MAXIMUM_TILES = 6;
-
- private int desiredTileWidth;
- private int desiredTilesHeight;
- private int tileMargin;
-
- public ActivityStream(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setBackgroundColor(ContextCompat.getColor(context, R.color.about_page_header_grey));
-
- inflate(context, R.layout.as_content, this);
-
- adapter = new StreamRecyclerAdapter();
-
- RecyclerView rv = (RecyclerView) findViewById(R.id.activity_stream_main_recyclerview);
-
- rv.setAdapter(adapter);
- rv.setLayoutManager(new LinearLayoutManager(getContext()));
- rv.setHasFixedSize(true);
-
- RecyclerViewClickSupport.addTo(rv)
- .setOnItemClickListener(adapter);
-
- final Resources resources = getResources();
- desiredTileWidth = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_width);
- desiredTilesHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_height);
- tileMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
- }
-
- void setOnUrlOpenListeners(HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- adapter.setOnUrlOpenListeners(onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- public void load(LoaderManager lm) {
- CursorLoaderCallbacks callbacks = new CursorLoaderCallbacks();
-
- lm.initLoader(LOADER_ID_HIGHLIGHTS, null, callbacks);
- lm.initLoader(LOADER_ID_TOPSITES, null, callbacks);
- }
-
- public void unload() {
- adapter.swapHighlightsCursor(null);
- adapter.swapTopSitesCursor(null);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- int tiles = (w - tileMargin) / (desiredTileWidth + tileMargin);
-
- if (tiles < MINIMUM_TILES) {
- tiles = MINIMUM_TILES;
-
- setPadding(0, 0, 0, 0);
- } else if (tiles > MAXIMUM_TILES) {
- tiles = MAXIMUM_TILES;
-
- // Use the remaining space as padding
- int needed = tiles * (desiredTileWidth + tileMargin) + tileMargin;
- int padding = (w - needed) / 2;
- w = needed;
-
- setPadding(padding, 0, padding, 0);
- } else {
- setPadding(0, 0, 0, 0);
- }
-
- final float ratio = (float) desiredTilesHeight / (float) desiredTileWidth;
- final int tilesWidth = (w - (tiles * tileMargin) - tileMargin) / tiles;
- final int tilesHeight = (int) (ratio * tilesWidth);
-
- adapter.setTileSize(tiles, tilesWidth, tilesHeight);
- }
-
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final Context context = getContext();
- if (id == LOADER_ID_HIGHLIGHTS) {
- return BrowserDB.from(context).getHighlights(context, 10);
- } else if (id == LOADER_ID_TOPSITES) {
- return BrowserDB.from(context).getActivityStreamTopSites(
- context, TopSitesPagerAdapter.PAGES * MAXIMUM_TILES);
- } else {
- throw new IllegalArgumentException("Can't handle loader id " + id);
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
- adapter.swapHighlightsCursor(data);
- } else if (loader.getId() == LOADER_ID_TOPSITES) {
- adapter.swapTopSitesCursor(data);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
- adapter.swapHighlightsCursor(null);
- } else if (loader.getId() == LOADER_ID_TOPSITES) {
- adapter.swapTopSitesCursor(null);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java
deleted file mode 100644
index 09f6705d7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomeFragment;
-
-/**
- * Simple wrapper around the ActivityStream view that allows embedding as a HomePager panel.
- */
-public class ActivityStreamHomeFragment
- extends HomeFragment {
- private ActivityStream activityStream;
-
- @Override
- protected void load() {
- activityStream.load(getLoaderManager());
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- if (activityStream == null) {
- activityStream = (ActivityStream) inflater.inflate(R.layout.activity_stream, container, false);
- activityStream.setOnUrlOpenListeners(mUrlOpenListener, mUrlOpenInBackgroundListener);
- }
-
- return activityStream;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java
deleted file mode 100644
index 4decc8218..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.util.AttributeSet;
-
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.home.HomeBanner;
-import org.mozilla.gecko.home.HomeFragment;
-import org.mozilla.gecko.home.HomeScreen;
-
-/**
- * HomeScreen implementation that displays ActivityStream.
- */
-public class ActivityStreamHomeScreen
- extends ActivityStream
- implements HomeScreen {
-
- private boolean visible = false;
-
- public ActivityStreamHomeScreen(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean isVisible() {
- return visible;
- }
-
- @Override
- public void onToolbarFocusChange(boolean hasFocus) {
-
- }
-
- @Override
- public void showPanel(String panelId, Bundle restoreData) {
-
- }
-
- @Override
- public void setOnPanelChangeListener(OnPanelChangeListener listener) {
-
- }
-
- @Override
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
-
- }
-
- @Override
- public void setBanner(HomeBanner banner) {
-
- }
-
- @Override
- public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData,
- PropertyAnimator animator) {
- super.load(lm);
- visible = true;
- }
-
- @Override
- public void unload() {
- super.unload();
- visible = false;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
deleted file mode 100644
index 24348dfe0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.support.v4.view.ViewPager;
-import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream.LabelCallback;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
-import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
-import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.ViewUtil;
-import org.mozilla.gecko.util.TouchTargetUtil;
-import org.mozilla.gecko.widget.FaviconView;
-
-import java.util.concurrent.Future;
-
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-public abstract class StreamItem extends RecyclerView.ViewHolder {
- public StreamItem(View itemView) {
- super(itemView);
- }
-
- public static class HighlightsTitle extends StreamItem {
- public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
-
- public HighlightsTitle(View itemView) {
- super(itemView);
- }
- }
-
- public static class TopPanel extends StreamItem {
- public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
-
- private final ViewPager topSitesPager;
-
- public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(itemView);
-
- topSitesPager = (ViewPager) itemView.findViewById(R.id.topsites_pager);
- topSitesPager.setAdapter(new TopSitesPagerAdapter(itemView.getContext(), onUrlOpenListener, onUrlOpenInBackgroundListener));
-
- CirclePageIndicator indicator = (CirclePageIndicator) itemView.findViewById(R.id.topsites_indicator);
- indicator.setViewPager(topSitesPager);
- }
-
- public void bind(Cursor cursor, int tiles, int tilesWidth, int tilesHeight) {
- final TopSitesPagerAdapter adapter = (TopSitesPagerAdapter) topSitesPager.getAdapter();
- adapter.setTilesSize(tiles, tilesWidth, tilesHeight);
- adapter.swapCursor(cursor);
-
- final Resources resources = itemView.getResources();
- final int tilesMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
- final int textHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
-
- ViewGroup.LayoutParams layoutParams = topSitesPager.getLayoutParams();
- layoutParams.height = tilesHeight + tilesMargin + textHeight;
- topSitesPager.setLayoutParams(layoutParams);
- }
- }
-
- public static class HighlightItem extends StreamItem implements IconCallback {
- public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
-
- String title;
- String url;
-
- final FaviconView vIconView;
- final TextView vLabel;
- final TextView vTimeSince;
- final TextView vSourceView;
- final TextView vPageView;
- final ImageView vSourceIconView;
-
- private Future<IconResponse> ongoingIconLoad;
- private int tilesMargin;
-
- public HighlightItem(final View itemView,
- final HomePager.OnUrlOpenListener onUrlOpenListener,
- final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(itemView);
-
- tilesMargin = itemView.getResources().getDimensionPixelSize(R.dimen.activity_stream_base_margin);
-
- vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
- vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
- vIconView = (FaviconView) itemView.findViewById(R.id.icon);
- vSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
- vPageView = (TextView) itemView.findViewById(R.id.page);
- vSourceIconView = (ImageView) itemView.findViewById(R.id.source_icon);
-
- final ImageView menuButton = (ImageView) itemView.findViewById(R.id.menu);
-
- menuButton.setImageDrawable(
- DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
-
- TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
-
- menuButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- ActivityStreamContextMenu.show(v.getContext(),
- menuButton,
- ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
- title, url, onUrlOpenListener, onUrlOpenInBackgroundListener,
- vIconView.getWidth(), vIconView.getHeight());
- }
- });
-
- ViewUtil.enableTouchRipple(menuButton);
- }
-
- public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
-
- final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
- final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
-
- title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE));
- url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
-
- vLabel.setText(title);
- vTimeSince.setText(ago);
-
- ViewGroup.LayoutParams layoutParams = vIconView.getLayoutParams();
- layoutParams.width = tilesWidth - tilesMargin;
- layoutParams.height = tilesHeight;
- vIconView.setLayoutParams(layoutParams);
-
- updateSource(cursor);
- updatePage(url);
-
- if (ongoingIconLoad != null) {
- ongoingIconLoad.cancel(true);
- }
-
- ongoingIconLoad = Icons.with(itemView.getContext())
- .pageUrl(url)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- private void updateSource(final Cursor cursor) {
- final boolean isBookmark = -1 != cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID));
- final boolean isHistory = -1 != cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
-
- if (isBookmark) {
- vSourceView.setText(R.string.activity_stream_highlight_label_bookmarked);
- vSourceView.setVisibility(View.VISIBLE);
- vSourceIconView.setImageResource(R.drawable.ic_as_bookmarked);
- } else if (isHistory) {
- vSourceView.setText(R.string.activity_stream_highlight_label_visited);
- vSourceView.setVisibility(View.VISIBLE);
- vSourceIconView.setImageResource(R.drawable.ic_as_visited);
- } else {
- vSourceView.setVisibility(View.INVISIBLE);
- vSourceIconView.setImageResource(0);
- }
-
- vSourceView.setText(vSourceView.getText());
- }
-
- private void updatePage(final String url) {
- extractLabel(itemView.getContext(), url, false, new LabelCallback() {
- @Override
- public void onLabelExtracted(String label) {
- vPageView.setText(TextUtils.isEmpty(label) ? url : label);
- }
- });
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- vIconView.updateImage(response);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
deleted file mode 100644
index f7cda2e7f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home.activitystream;
-
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
-import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> implements RecyclerViewClickSupport.OnItemClickListener {
- private Cursor highlightsCursor;
- private Cursor topSitesCursor;
-
- private HomePager.OnUrlOpenListener onUrlOpenListener;
- private HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
-
- void setOnUrlOpenListeners(HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- public void setTileSize(int tiles, int tilesWidth, int tilesHeight) {
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.tiles = tiles;
-
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position == 0) {
- return TopPanel.LAYOUT_ID;
- } else if (position == 1) {
- return StreamItem.HighlightsTitle.LAYOUT_ID;
- } else {
- return HighlightItem.LAYOUT_ID;
- }
- }
-
- @Override
- public StreamItem onCreateViewHolder(ViewGroup parent, final int type) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
- if (type == TopPanel.LAYOUT_ID) {
- return new TopPanel(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
- } else if (type == StreamItem.HighlightsTitle.LAYOUT_ID) {
- return new StreamItem.HighlightsTitle(inflater.inflate(type, parent, false));
- } else if (type == HighlightItem.LAYOUT_ID) {
- return new HighlightItem(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
- } else {
- throw new IllegalStateException("Missing inflation for ViewType " + type);
- }
- }
-
- private int translatePositionToCursor(int position) {
- if (position == 0) {
- throw new IllegalArgumentException("Requested cursor position for invalid item");
- }
-
- // We have two blank panels at the top, hence remove that to obtain the cursor position
- return position - 2;
- }
-
- @Override
- public void onBindViewHolder(StreamItem holder, int position) {
- int type = getItemViewType(position);
-
- if (type == HighlightItem.LAYOUT_ID) {
- final int cursorPosition = translatePositionToCursor(position);
-
- highlightsCursor.moveToPosition(cursorPosition);
- ((HighlightItem) holder).bind(highlightsCursor, tilesWidth, tilesHeight);
- } else if (type == TopPanel.LAYOUT_ID) {
- ((TopPanel) holder).bind(topSitesCursor, tiles, tilesWidth, tilesHeight);
- }
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (position < 1) {
- // The header contains top sites and has its own click handling.
- return;
- }
-
- highlightsCursor.moveToPosition(
- translatePositionToCursor(position));
-
- final String url = highlightsCursor.getString(
- highlightsCursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
-
- onUrlOpenListener.onUrlOpen(url, EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
-
- @Override
- public int getItemCount() {
- final int highlightsCount;
-
- if (highlightsCursor != null) {
- highlightsCount = highlightsCursor.getCount();
- } else {
- highlightsCount = 0;
- }
-
- return highlightsCount + 2;
- }
-
- public void swapHighlightsCursor(Cursor cursor) {
- highlightsCursor = cursor;
-
- notifyDataSetChanged();
- }
-
- public void swapTopSitesCursor(Cursor cursor) {
- this.topSitesCursor = cursor;
-
- notifyItemChanged(0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
deleted file mode 100644
index 525d3b426..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.design.widget.NavigationView;
-import android.view.MenuItem;
-import android.view.View;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.IntentHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import java.util.EnumSet;
-
-@RobocopTarget
-public abstract class ActivityStreamContextMenu
- implements NavigationView.OnNavigationItemSelectedListener {
-
- public enum MenuMode {
- HIGHLIGHT,
- TOPSITE
- }
-
- final Context context;
-
- final String title;
- final String url;
-
- final HomePager.OnUrlOpenListener onUrlOpenListener;
- final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- boolean isAlreadyBookmarked; // default false;
-
- public abstract MenuItem getItemByID(int id);
-
- public abstract void show();
-
- public abstract void dismiss();
-
- final MenuMode mode;
-
- /* package-private */ ActivityStreamContextMenu(final Context context,
- final MenuMode mode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.context = context;
-
- this.mode = mode;
-
- this.title = title;
- this.url = url;
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- /**
- * Must be called before the menu is shown.
- * <p/>
- * Your implementation must be ready to return items from getItemByID() before postInit() is
- * called, i.e. you should probably inflate your menu items before this call.
- */
- protected void postInit() {
- // Disable "dismiss" for topsites until we have decided on its behaviour for topsites
- // (currently "dismiss" adds the URL to a highlights-specific blocklist, which the topsites
- // query has no knowledge of).
- if (mode == MenuMode.TOPSITE) {
- final MenuItem dismissItem = getItemByID(R.id.dismiss);
- dismissItem.setVisible(false);
- }
-
- // Disable the bookmark item until we know its bookmark state
- final MenuItem bookmarkItem = getItemByID(R.id.bookmark);
- bookmarkItem.setEnabled(false);
-
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- @Override
- protected Void doInBackground() {
- isAlreadyBookmarked = BrowserDB.from(context).isBookmark(context.getContentResolver(), url);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- if (isAlreadyBookmarked) {
- bookmarkItem.setTitle(R.string.bookmark_remove);
- }
-
- bookmarkItem.setEnabled(true);
- }
- }).execute();
-
- // Only show the "remove from history" item if a page actually has history
- final MenuItem deleteHistoryItem = getItemByID(R.id.delete);
- deleteHistoryItem.setVisible(false);
-
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- boolean hasHistory;
-
- @Override
- protected Void doInBackground() {
- final Cursor cursor = BrowserDB.from(context).getHistoryForURL(context.getContentResolver(), url);
- try {
- if (cursor != null &&
- cursor.getCount() == 1) {
- hasHistory = true;
- } else {
- hasHistory = false;
- }
- } finally {
- cursor.close();
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- if (hasHistory) {
- deleteHistoryItem.setVisible(true);
- }
- }
- }).execute();
- }
-
-
- @Override
- public boolean onNavigationItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.share:
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
- IntentHelper.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title, false);
- break;
-
- case R.id.bookmark:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final BrowserDB db = BrowserDB.from(context);
-
- if (isAlreadyBookmarked) {
- db.removeBookmarksWithURL(context.getContentResolver(), url);
- } else {
- db.addBookmark(context.getContentResolver(), title, url);
- }
-
- }
- });
- break;
-
- case R.id.copy_url:
- Clipboard.setText(url);
- break;
-
- case R.id.add_homescreen:
- GeckoAppShell.createShortcut(title, url);
- break;
-
- case R.id.open_new_tab:
- onUrlOpenInBackgroundListener.onUrlOpenInBackground(url, EnumSet.noneOf(HomePager.OnUrlOpenInBackgroundListener.Flags.class));
- break;
-
- case R.id.open_new_private_tab:
- onUrlOpenInBackgroundListener.onUrlOpenInBackground(url, EnumSet.of(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE));
- break;
-
- case R.id.dismiss:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- BrowserDB.from(context)
- .blockActivityStreamSite(context.getContentResolver(),
- url);
- }
- });
- break;
-
- case R.id.delete:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- BrowserDB.from(context)
- .removeHistoryEntry(context.getContentResolver(),
- url);
- }
- });
- break;
-
- default:
- throw new IllegalArgumentException("Menu item with ID=" + item.getItemId() + " not handled");
- }
-
- dismiss();
- return true;
- }
-
-
- @RobocopTarget
- public static ActivityStreamContextMenu show(Context context,
- View anchor,
- final MenuMode menuMode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
- final ActivityStreamContextMenu menu;
-
- if (!HardwareUtils.isTablet()) {
- menu = new BottomSheetContextMenu(context,
- menuMode,
- title, url,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- tilesWidth, tilesHeight);
- } else {
- menu = new PopupContextMenu(context,
- anchor,
- menuMode,
- title, url,
- onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- menu.show();
- return menu;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java
deleted file mode 100644
index e95867c36..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.design.widget.BottomSheetBehavior;
-import android.support.design.widget.BottomSheetDialog;
-import android.support.design.widget.NavigationView;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.widget.FaviconView;
-
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-/* package-private */ class BottomSheetContextMenu
- extends ActivityStreamContextMenu {
-
-
- private final BottomSheetDialog bottomSheetDialog;
-
- private final NavigationView navigationView;
-
- public BottomSheetContextMenu(final Context context,
- final MenuMode mode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
-
- super(context,
- mode,
- title,
- url,
- onUrlOpenListener,
- onUrlOpenInBackgroundListener);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- final View content = inflater.inflate(R.layout.activity_stream_contextmenu_bottomsheet, null);
-
- bottomSheetDialog = new BottomSheetDialog(context);
- bottomSheetDialog.setContentView(content);
-
- ((TextView) content.findViewById(R.id.title)).setText(title);
-
- extractLabel(context, url, false, new ActivityStream.LabelCallback() {
- public void onLabelExtracted(String label) {
- ((TextView) content.findViewById(R.id.url)).setText(label);
- }
- });
-
- // Copy layouted parameters from the Highlights / TopSites items to ensure consistency
- final FaviconView faviconView = (FaviconView) content.findViewById(R.id.icon);
- ViewGroup.LayoutParams layoutParams = faviconView.getLayoutParams();
- layoutParams.width = tilesWidth;
- layoutParams.height = tilesHeight;
- faviconView.setLayoutParams(layoutParams);
-
- Icons.with(context)
- .pageUrl(url)
- .skipNetwork()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- faviconView.updateImage(response);
- }
- });
-
- navigationView = (NavigationView) content.findViewById(R.id.menu);
- navigationView.setNavigationItemSelectedListener(this);
-
- super.postInit();
- }
-
- @Override
- public MenuItem getItemByID(int id) {
- return navigationView.getMenu().findItem(id);
- }
-
- @Override
- public void show() {
- bottomSheetDialog.show();
- }
-
- public void dismiss() {
- bottomSheetDialog.dismiss();
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
deleted file mode 100644
index 56615937b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.support.annotation.NonNull;
-import android.support.design.widget.NavigationView;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.PopupWindow;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager;
-
-/* package-private */ class PopupContextMenu
- extends ActivityStreamContextMenu {
-
- private final PopupWindow popupWindow;
- private final NavigationView navigationView;
-
- private final View anchor;
-
- public PopupContextMenu(final Context context,
- View anchor,
- final MenuMode mode,
- final String title,
- @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(context,
- mode,
- title,
- url,
- onUrlOpenListener,
- onUrlOpenInBackgroundListener);
-
- this.anchor = anchor;
-
- final LayoutInflater inflater = LayoutInflater.from(context);
-
- View card = inflater.inflate(R.layout.activity_stream_contextmenu_popupmenu, null);
- navigationView = (NavigationView) card.findViewById(R.id.menu);
- navigationView.setNavigationItemSelectedListener(this);
-
- popupWindow = new PopupWindow(context);
- popupWindow.setContentView(card);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setFocusable(true);
-
- super.postInit();
- }
-
- @Override
- public MenuItem getItemByID(int id) {
- return navigationView.getMenu().findItem(id);
- }
-
- @Override
- public void show() {
- // By default popupWindow follows the pre-material convention of displaying the popup
- // below a View. We need to shift it over the view:
- popupWindow.showAsDropDown(anchor,
- 0,
- -(anchor.getHeight() + anchor.getPaddingBottom()));
- }
-
- public void dismiss() {
- popupWindow.dismiss();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
deleted file mode 100644
index 096f0c597..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * Copyright (C) 2011 Patrik Akerfeldt
- * Copyright (C) 2011 Jake Wharton
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.view.MotionEventCompat;
-import android.support.v4.view.ViewConfigurationCompat;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import org.mozilla.gecko.R;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.widget.LinearLayout.HORIZONTAL;
-import static android.widget.LinearLayout.VERTICAL;
-
-/**
- * Draws circles (one for each view). The current view position is filled and
- * others are only stroked.
- *
- * This file was imported from Jake Wharton's ViewPagerIndicator library:
- * https://github.com/JakeWharton/ViewPagerIndicator
- * It was modified to not extend the PageIndicator interface (as we only use one single Indicator)
- * implementation, and has had some minor appearance related modifications added alter.
- */
-public class CirclePageIndicator
- extends View
- implements ViewPager.OnPageChangeListener {
-
- /**
- * Separation between circles, as a factor of the circle radius. By default CirclePageIndicator
- * shipped with a separation factor of 3, however we want to be able to tweak this for
- * ActivityStream.
- *
- * If/when we reuse this indicator elsewhere, this should probably become a configurable property.
- */
- private static final int SEPARATION_FACTOR = 7;
-
- private static final int INVALID_POINTER = -1;
-
- private float mRadius;
- private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
- private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
- private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
- private ViewPager mViewPager;
- private ViewPager.OnPageChangeListener mListener;
- private int mCurrentPage;
- private int mSnapPage;
- private float mPageOffset;
- private int mScrollState;
- private int mOrientation;
- private boolean mCentered;
- private boolean mSnap;
-
- private int mTouchSlop;
- private float mLastMotionX = -1;
- private int mActivePointerId = INVALID_POINTER;
- private boolean mIsDragging;
-
-
- public CirclePageIndicator(Context context) {
- this(context, null);
- }
-
- public CirclePageIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.vpiCirclePageIndicatorStyle);
- }
-
- public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- if (isInEditMode()) return;
-
- //Load defaults from resources
- final Resources res = getResources();
- final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
- final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
- final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
- final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
- final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
- final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
- final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
- final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);
-
- //Retrieve styles attributes
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
-
- mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
- mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
- mPaintPageFill.setStyle(Style.FILL);
- mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
- mPaintStroke.setStyle(Style.STROKE);
- mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
- mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
- mPaintFill.setStyle(Style.FILL);
- mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor));
- mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius);
- mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);
-
- Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
- if (background != null) {
- setBackgroundDrawable(background);
- }
-
- a.recycle();
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
- }
-
-
- public void setCentered(boolean centered) {
- mCentered = centered;
- invalidate();
- }
-
- public boolean isCentered() {
- return mCentered;
- }
-
- public void setPageColor(int pageColor) {
- mPaintPageFill.setColor(pageColor);
- invalidate();
- }
-
- public int getPageColor() {
- return mPaintPageFill.getColor();
- }
-
- public void setFillColor(int fillColor) {
- mPaintFill.setColor(fillColor);
- invalidate();
- }
-
- public int getFillColor() {
- return mPaintFill.getColor();
- }
-
- public void setOrientation(int orientation) {
- switch (orientation) {
- case HORIZONTAL:
- case VERTICAL:
- mOrientation = orientation;
- requestLayout();
- break;
-
- default:
- throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
- }
- }
-
- public int getOrientation() {
- return mOrientation;
- }
-
- public void setStrokeColor(int strokeColor) {
- mPaintStroke.setColor(strokeColor);
- invalidate();
- }
-
- public int getStrokeColor() {
- return mPaintStroke.getColor();
- }
-
- public void setStrokeWidth(float strokeWidth) {
- mPaintStroke.setStrokeWidth(strokeWidth);
- invalidate();
- }
-
- public float getStrokeWidth() {
- return mPaintStroke.getStrokeWidth();
- }
-
- public void setRadius(float radius) {
- mRadius = radius;
- invalidate();
- }
-
- public float getRadius() {
- return mRadius;
- }
-
- public void setSnap(boolean snap) {
- mSnap = snap;
- invalidate();
- }
-
- public boolean isSnap() {
- return mSnap;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (mViewPager == null) {
- return;
- }
- final int count = mViewPager.getAdapter().getCount();
- if (count == 0) {
- return;
- }
-
- if (mCurrentPage >= count) {
- setCurrentItem(count - 1);
- return;
- }
-
- int longSize;
- int longPaddingBefore;
- int longPaddingAfter;
- int shortPaddingBefore;
- if (mOrientation == HORIZONTAL) {
- longSize = getWidth();
- longPaddingBefore = getPaddingLeft();
- longPaddingAfter = getPaddingRight();
- shortPaddingBefore = getPaddingTop();
- } else {
- longSize = getHeight();
- longPaddingBefore = getPaddingTop();
- longPaddingAfter = getPaddingBottom();
- shortPaddingBefore = getPaddingLeft();
- }
-
- final float threeRadius = mRadius * SEPARATION_FACTOR;
- final float shortOffset = shortPaddingBefore + mRadius;
- float longOffset = longPaddingBefore + mRadius;
- if (mCentered) {
- longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
- }
-
- float dX;
- float dY;
-
- float pageFillRadius = mRadius;
- if (mPaintStroke.getStrokeWidth() > 0) {
- pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
- }
-
- //Draw stroked circles
- for (int iLoop = 0; iLoop < count; iLoop++) {
- float drawLong = longOffset + (iLoop * threeRadius);
- if (mOrientation == HORIZONTAL) {
- dX = drawLong;
- dY = shortOffset;
- } else {
- dX = shortOffset;
- dY = drawLong;
- }
- // Only paint fill if not completely transparent
- if (mPaintPageFill.getAlpha() > 0) {
- canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
- }
-
- // Only paint stroke if a stroke width was non-zero
- if (pageFillRadius != mRadius) {
- canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
- }
- }
-
- //Draw the filled circle according to the current scroll
- float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
- if (!mSnap) {
- cx += mPageOffset * threeRadius;
- }
- if (mOrientation == HORIZONTAL) {
- dX = longOffset + cx;
- dY = shortOffset;
- } else {
- dX = shortOffset;
- dY = longOffset + cx;
- }
- canvas.drawCircle(dX, dY, mRadius, mPaintFill);
- }
-
- public boolean onTouchEvent(android.view.MotionEvent ev) {
- if (super.onTouchEvent(ev)) {
- return true;
- }
- if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
- return false;
- }
-
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mLastMotionX = ev.getX();
- break;
-
- case MotionEvent.ACTION_MOVE: {
- final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float x = MotionEventCompat.getX(ev, activePointerIndex);
- final float deltaX = x - mLastMotionX;
-
- if (!mIsDragging) {
- if (Math.abs(deltaX) > mTouchSlop) {
- mIsDragging = true;
- }
- }
-
- if (mIsDragging) {
- mLastMotionX = x;
- if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
- mViewPager.fakeDragBy(deltaX);
- }
- }
-
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (!mIsDragging) {
- final int count = mViewPager.getAdapter().getCount();
- final int width = getWidth();
- final float halfWidth = width / 2f;
- final float sixthWidth = width / 6f;
-
- if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
- if (action != MotionEvent.ACTION_CANCEL) {
- mViewPager.setCurrentItem(mCurrentPage - 1);
- }
- return true;
- } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
- if (action != MotionEvent.ACTION_CANCEL) {
- mViewPager.setCurrentItem(mCurrentPage + 1);
- }
- return true;
- }
- }
-
- mIsDragging = false;
- mActivePointerId = INVALID_POINTER;
- if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
- break;
-
- case MotionEventCompat.ACTION_POINTER_DOWN: {
- final int index = MotionEventCompat.getActionIndex(ev);
- mLastMotionX = MotionEventCompat.getX(ev, index);
- mActivePointerId = MotionEventCompat.getPointerId(ev, index);
- break;
- }
-
- case MotionEventCompat.ACTION_POINTER_UP:
- final int pointerIndex = MotionEventCompat.getActionIndex(ev);
- final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
- if (pointerId == mActivePointerId) {
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
- }
- mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
- break;
- }
-
- return true;
- }
-
- public void setViewPager(ViewPager view) {
- if (mViewPager == view) {
- return;
- }
- if (mViewPager != null) {
- mViewPager.setOnPageChangeListener(null);
- }
- if (view.getAdapter() == null) {
- throw new IllegalStateException("ViewPager does not have adapter instance.");
- }
- mViewPager = view;
- mViewPager.setOnPageChangeListener(this);
- invalidate();
- }
-
- public void setViewPager(ViewPager view, int initialPosition) {
- setViewPager(view);
- setCurrentItem(initialPosition);
- }
-
- public void setCurrentItem(int item) {
- if (mViewPager == null) {
- throw new IllegalStateException("ViewPager has not been bound.");
- }
- mViewPager.setCurrentItem(item);
- mCurrentPage = item;
- invalidate();
- }
-
- public void notifyDataSetChanged() {
- invalidate();
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- mScrollState = state;
-
- if (mListener != null) {
- mListener.onPageScrollStateChanged(state);
- }
- }
-
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- mCurrentPage = position;
- mPageOffset = positionOffset;
- invalidate();
-
- if (mListener != null) {
- mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
- }
-
- @Override
- public void onPageSelected(int position) {
- if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
- mCurrentPage = position;
- mSnapPage = position;
- invalidate();
- }
-
- if (mListener != null) {
- mListener.onPageSelected(position);
- }
- }
-
- public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
- mListener = listener;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see android.view.View#onMeasure(int, int)
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mOrientation == HORIZONTAL) {
- setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
- } else {
- setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
- }
- }
-
- /**
- * Determines the width of this view
- *
- * @param measureSpec
- * A measureSpec packed into an int
- * @return The width of the view, honoring constraints from measureSpec
- */
- private int measureLong(int measureSpec) {
- int result;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
- //We were told how big to be
- result = specSize;
- } else {
- //Calculate the width according the views count
- final int count = mViewPager.getAdapter().getCount();
- result = (int)(getPaddingLeft() + getPaddingRight()
- + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
- //Respect AT_MOST value if that was what is called for by measureSpec
- if (specMode == MeasureSpec.AT_MOST) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
-
- /**
- * Determines the height of this view
- *
- * @param measureSpec
- * A measureSpec packed into an int
- * @return The height of the view, honoring constraints from measureSpec
- */
- private int measureShort(int measureSpec) {
- int result;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- if (specMode == MeasureSpec.EXACTLY) {
- //We were told how big to be
- result = specSize;
- } else {
- //Measure the height
- result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
- //Respect AT_MOST value if that was what is called for by measureSpec
- if (specMode == MeasureSpec.AT_MOST) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- SavedState savedState = (SavedState)state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mCurrentPage = savedState.currentPage;
- mSnapPage = savedState.currentPage;
- requestLayout();
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- SavedState savedState = new SavedState(superState);
- savedState.currentPage = mCurrentPage;
- return savedState;
- }
-
- static class SavedState extends BaseSavedState {
- int currentPage;
-
- public SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- currentPage = in.readInt();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(currentPage);
- }
-
- @SuppressWarnings("UnusedDeclaration")
- public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
deleted file mode 100644
index b436a466f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.graphics.Color;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.ViewUtil;
-import org.mozilla.gecko.util.TouchTargetUtil;
-import org.mozilla.gecko.widget.FaviconView;
-
-import java.util.EnumSet;
-import java.util.concurrent.Future;
-
-class TopSitesCard extends RecyclerView.ViewHolder
- implements IconCallback, View.OnClickListener {
- private final FaviconView faviconView;
-
- private final TextView title;
- private final ImageView menuButton;
- private Future<IconResponse> ongoingIconLoad;
-
- private String url;
-
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesCard(FrameLayout card, final HomePager.OnUrlOpenListener onUrlOpenListener, final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(card);
-
- faviconView = (FaviconView) card.findViewById(R.id.favicon);
-
- title = (TextView) card.findViewById(R.id.title);
- menuButton = (ImageView) card.findViewById(R.id.menu);
-
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
-
- card.setOnClickListener(this);
-
- TouchTargetUtil.ensureTargetHitArea(menuButton, card);
- menuButton.setOnClickListener(this);
-
- ViewUtil.enableTouchRipple(menuButton);
- }
-
- void bind(final TopSitesPageAdapter.TopSite topSite) {
- ActivityStream.extractLabel(itemView.getContext(), topSite.url, true, new ActivityStream.LabelCallback() {
- @Override
- public void onLabelExtracted(String label) {
- title.setText(label);
- }
- });
-
- this.url = topSite.url;
-
- if (ongoingIconLoad != null) {
- ongoingIconLoad.cancel(true);
- }
-
- ongoingIconLoad = Icons.with(itemView.getContext())
- .pageUrl(topSite.url)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- faviconView.updateImage(response);
-
- final int tintColor = !response.hasColor() || response.getColor() == Color.WHITE ? Color.LTGRAY : Color.WHITE;
-
- menuButton.setImageDrawable(
- DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, tintColor));
- }
-
- @Override
- public void onClick(View clickedView) {
- if (clickedView == itemView) {
- onUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
- } else if (clickedView == menuButton) {
- ActivityStreamContextMenu.show(clickedView.getContext(),
- menuButton,
- ActivityStreamContextMenu.MenuMode.TOPSITE,
- title.getText().toString(), url,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- faviconView.getWidth(), faviconView.getHeight());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java
deleted file mode 100644
index 45fdc0d1a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.View;
-
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-public class TopSitesPage
- extends RecyclerView {
- public TopSitesPage(Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
-
- setLayoutManager(new GridLayoutManager(context, 1));
- }
-
- public void setTiles(int tiles) {
- setLayoutManager(new GridLayoutManager(getContext(), tiles));
- }
-
- private HomePager.OnUrlOpenListener onUrlOpenListener;
- private HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesPageAdapter getAdapter() {
- return (TopSitesPageAdapter) super.getAdapter();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
deleted file mode 100644
index 29e6aca3d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.annotation.UiThread;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
- static final class TopSite {
- public final long id;
- public final String url;
- public final String title;
-
- TopSite(long id, String url, String title) {
- this.id = id;
- this.url = url;
- this.title = title;
- }
- }
-
- private List<TopSite> topSites;
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
- private int textHeight;
-
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesPageAdapter(Context context, int tiles, int tilesWidth, int tilesHeight,
- HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- setHasStableIds(true);
-
- this.topSites = new ArrayList<>();
- this.tiles = tiles;
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.textHeight = context.getResources().getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
-
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- /**
- *
- * @param cursor
- * @param startIndex The first item that this topsites group should show. This item, and the following
- * 3 items will be displayed by this adapter.
- */
- public void swapCursor(Cursor cursor, int startIndex) {
- topSites.clear();
-
- if (cursor == null) {
- return;
- }
-
- for (int i = 0; i < tiles && startIndex + i < cursor.getCount(); i++) {
- cursor.moveToPosition(startIndex + i);
-
- // The Combined View only contains pages that have been visited at least once, i.e. any
- // page in the TopSites query will contain a HISTORY_ID. _ID however will be 0 for all rows.
- final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- final String title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
-
- topSites.add(new TopSite(id, url, title));
- }
-
- notifyDataSetChanged();
- }
-
- @Override
- public void onBindViewHolder(TopSitesCard holder, int position) {
- holder.bind(topSites.get(position));
- }
-
- @Override
- public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
- final FrameLayout card = (FrameLayout) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
- final View content = card.findViewById(R.id.content);
-
- ViewGroup.LayoutParams layoutParams = content.getLayoutParams();
- layoutParams.width = tilesWidth;
- layoutParams.height = tilesHeight + textHeight;
- content.setLayoutParams(layoutParams);
-
- return new TopSitesCard(card, onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- @Override
- public int getItemCount() {
- return topSites.size();
- }
-
- @Override
- @UiThread
- public long getItemId(int position) {
- return topSites.get(position).id;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
deleted file mode 100644
index dc824d902..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.view.PagerAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager;
-
-import java.util.LinkedList;
-
-/**
- * The primary / top-level TopSites adapter: it handles the ViewPager, and also handles
- * all lower-level Adapters that populate the individual topsite items.
- */
-public class TopSitesPagerAdapter extends PagerAdapter {
- public static final int PAGES = 4;
-
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
-
- private LinkedList<TopSitesPage> pages = new LinkedList<>();
-
- private final Context context;
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- private int count = 0;
-
- public TopSitesPagerAdapter(Context context,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.context = context;
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- public void setTilesSize(int tiles, int tilesWidth, int tilesHeight) {
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.tiles = tiles;
- }
-
- @Override
- public int getCount() {
- return Math.min(count, 4);
- }
-
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
-
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- TopSitesPage page = pages.get(position);
-
- container.addView(page);
-
- return page;
- }
-
- @Override
- public int getItemPosition(Object object) {
- return PagerAdapter.POSITION_NONE;
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView((View) object);
- }
-
- public void swapCursor(Cursor cursor) {
- // Divide while rounding up: 0 items = 0 pages, 1-ITEMS_PER_PAGE items = 1 page, etc.
- if (cursor != null) {
- count = (cursor.getCount() - 1) / tiles + 1;
- } else {
- count = 0;
- }
-
- pages.clear();
- final int pageDelta = count;
-
- if (pageDelta > 0) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- for (int i = 0; i < pageDelta; i++) {
- final TopSitesPage page = (TopSitesPage) inflater.inflate(R.layout.activity_stream_topsites_page, null, false);
-
- page.setTiles(tiles);
- final TopSitesPageAdapter adapter = new TopSitesPageAdapter(context, tiles, tilesWidth, tilesHeight,
- onUrlOpenListener, onUrlOpenInBackgroundListener);
- page.setAdapter(adapter);
- pages.add(page);
- }
- } else if (pageDelta < 0) {
- for (int i = 0; i > pageDelta; i--) {
- final TopSitesPage page = pages.getLast();
-
- // Ensure the page doesn't use the old/invalid cursor anymore
- page.getAdapter().swapCursor(null, 0);
-
- pages.removeLast();
- }
- } else {
- // do nothing: we will be updating all the pages below
- }
-
- int startIndex = 0;
- for (TopSitesPage page : pages) {
- page.getAdapter().swapCursor(cursor, startIndex);
- startIndex += tiles;
- }
-
- notifyDataSetChanged();
- }
-}