Bug 1220928 - Add client hiding/showing. r=sebastian
MozReview-Commit-ID: IvyrslMqTOT
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
@@ -10,31 +10,33 @@ import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.json.JSONArray;
+import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.RemoteTab;
import org.mozilla.gecko.home.CombinedHistoryPanel.SectionHeader;
import java.util.Collections;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistoryItem> {
private static final String LOGTAG = "GeckoCombinedHistAdapt";
public enum ItemType {
- CLIENT, SECTION_HEADER, HISTORY, NAVIGATION_BACK, CHILD;
+ CLIENT, HIDDEN_DEVICES, SECTION_HEADER, HISTORY, NAVIGATION_BACK, CHILD;
public static ItemType viewTypeToItemType(int viewType) {
if (viewType >= ItemType.values().length) {
Log.e(LOGTAG, "No corresponding ItemType!");
}
return ItemType.values()[viewType];
}
@@ -42,38 +44,138 @@ public class CombinedHistoryAdapter exte
return itemType.ordinal();
}
}
private List<RemoteClient> remoteClients = Collections.emptyList();
private List<RemoteTab> clientChildren;
private Cursor historyCursor;
+ // Maintain group collapsed and hidden state. Only accessed from the UI thread.
+ protected static RemoteTabsExpandableListState sState;
+
+ // List of hidden remote clients.
+ // Only accessed from the UI thread.
+ protected final List<RemoteClient> hiddenClients = new ArrayList<>();
+
// We use a sparse array to store each section header's position in the panel [more cheaply than a HashMap].
private final SparseArray<CombinedHistoryPanel.SectionHeader> sectionHeaders;
private final Context context;
private boolean inChildView = false;
public CombinedHistoryAdapter(Context context) {
super();
this.context = context;
sectionHeaders = new SparseArray<>();
+
+ // 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));
+ }
}
public void setClients(List<RemoteClient> clients) {
+ hiddenClients.clear();
+ remoteClients.clear();
+
+ final Iterator<RemoteClient> it = clients.iterator();
+ while (it.hasNext()) {
+ final RemoteClient client = it.next();
+ if (sState.isClientHidden(client.guid)) {
+ hiddenClients.add(client);
+ it.remove();
+ }
+ }
+
remoteClients = clients;
- notifyDataSetChanged();
+
+ // Add item for unhiding clients.
+ if (!hiddenClients.isEmpty()) {
+ remoteClients.add(null);
+ }
+
+ notifyItemRangeChanged(0, remoteClients.size());
}
public void setHistory(Cursor history) {
historyCursor = history;
populateSectionHeaders(historyCursor, sectionHeaders);
- notifyDataSetChanged();
+ final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
+ notifyItemRangeChanged(remoteClients.size(), historySize + sectionHeaders.size());
+ }
+
+ public void removeItem(int position) {
+ final ItemType itemType = getItemTypeForPosition(position);
+ switch (itemType) {
+ case CLIENT:
+ final boolean hadHiddenClients = !hiddenClients.isEmpty();
+ final RemoteClient client = remoteClients.remove(transformAdapterPositionForDataStructure(ItemType.CLIENT, position));
+ notifyItemRemoved(position);
+
+ sState.setClientHidden(client.guid, true);
+ hiddenClients.add(client);
+ if (!hadHiddenClients) {
+ // Add item for unhiding clients;
+ remoteClients.add(null);
+ } else {
+ // Update "hidden clients" item because number of hidden clients changed.
+ notifyItemChanged(getRemoteClientsHiddenItemsIndex());
+ }
+ break;
+ }
+ }
+
+ public void unhideClients(List<RemoteClient> selectedClients) {
+ if (selectedClients.size() == 0) {
+ return;
+ }
+
+ for (RemoteClient client : selectedClients) {
+ sState.setClientHidden(client.guid, false);
+ hiddenClients.remove(client);
+ }
+
+ final int insertIndex = getRemoteClientsHiddenItemsIndex();
+
+ remoteClients.addAll(insertIndex, selectedClients);
+ notifyItemRangeInserted(insertIndex, selectedClients.size());
+
+ if (hiddenClients.isEmpty()) {
+ // No more hidden clients, remove "unhide" item.
+ remoteClients.remove(getRemoteClientsHiddenItemsIndex());
+ } else {
+ // Update "hidden clients" item because number of hidden clients changed.
+ notifyItemChanged(getRemoteClientsHiddenItemsIndex());
+ }
+ }
+
+ /**
+ * Get the position of the "N devices hidden" item in the remoteClients List.
+ *
+ * This is the last item in the remoteClients list, if any items are hidden.
+ * <code>hiddenClients</code> must be in a consistent state with <code>remoteClients</code>
+ * (e.g. each client should be in exactly one of the two lists).
+ *
+ * @return index of the "N devices hidden" item, or -1 if it doesn't exist.
+ */
+ private int getRemoteClientsHiddenItemsIndex() {
+ if (hiddenClients.isEmpty()) {
+ return -1;
+ }
+ return remoteClients.size() - 1;
+ }
+
+ public List<RemoteClient> getHiddenClients() {
+ return hiddenClients;
}
public JSONArray getCurrentChildTabs() {
if (clientChildren != null) {
final JSONArray urls = new JSONArray();
for (int i = 1; i < clientChildren.size(); i++) {
urls.put(clientChildren.get(i).url);
}
@@ -83,73 +185,95 @@ public class CombinedHistoryAdapter exte
}
public void showChildView(int parentPosition) {
if (clientChildren == null) {
clientChildren = new ArrayList<>();
}
// Handle "back" view.
clientChildren.add(null);
- clientChildren.addAll(remoteClients.get(transformPosition(ItemType.CLIENT, parentPosition)).tabs);
+ clientChildren.addAll(remoteClients.get(transformAdapterPositionForDataStructure(ItemType.CLIENT, parentPosition)).tabs);
inChildView = true;
notifyDataSetChanged();
}
public void exitChildView() {
inChildView = false;
clientChildren.clear();
notifyDataSetChanged();
}
- private int transformPosition(ItemType type, int position) {
+ private ItemType getItemTypeForPosition(int position) {
+ return ItemType.viewTypeToItemType(getItemViewType(position));
+ }
+
+ /**
+ * 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
+ */
+ private int transformAdapterPositionForDataStructure(ItemType type, int position) {
if (type == ItemType.CLIENT) {
return position;
} else if (type == ItemType.SECTION_HEADER) {
return position - remoteClients.size();
} else if (type == ItemType.HISTORY){
return position - remoteClients.size() - getHeadersBefore(position);
} else {
return position;
}
}
public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final ItemType itemType = ItemType.viewTypeToItemType(getItemViewType(position));
+ final ItemType itemType = getItemTypeForPosition(position);
HomeContextMenuInfo info;
switch (itemType) {
case CHILD:
info = new HomeContextMenuInfo(view, position, -1);
return CombinedHistoryPanel.populateChildInfoFromTab(info, clientChildren.get(position));
case HISTORY:
info = new HomeContextMenuInfo(view, position, -1);
- historyCursor.moveToPosition(transformPosition(ItemType.HISTORY, position));
+ historyCursor.moveToPosition(transformAdapterPositionForDataStructure(ItemType.HISTORY, position));
return CombinedHistoryPanel.populateHistoryInfoFromCursor(info, historyCursor);
+ case CLIENT:
+ final int clientPosition = transformAdapterPositionForDataStructure(ItemType.CLIENT, position);
+ info = new CombinedHistoryPanel.RemoteTabsClientContextMenuInfo(view, position,-1, remoteClients.get(clientPosition));
+ return info;
}
return null;
}
@Override
public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
final View view;
final ItemType itemType = ItemType.viewTypeToItemType(viewType);
switch (itemType) {
case CLIENT:
view = inflater.inflate(R.layout.home_remote_tabs_group, viewGroup, false);
return new CombinedHistoryItem.ClientItem(view);
+ case HIDDEN_DEVICES:
+ view = inflater.inflate(R.layout.home_remote_tabs_hidden_devices, viewGroup, false);
+ return new CombinedHistoryItem.BasicItem(view);
+
case NAVIGATION_BACK:
view = inflater.inflate(R.layout.home_combined_back_item, viewGroup, false);
return new CombinedHistoryItem.HistoryItem(view);
case SECTION_HEADER:
view = inflater.inflate(R.layout.home_header_row, viewGroup, false);
- return new CombinedHistoryItem.SectionItem(view);
+ return new CombinedHistoryItem.BasicItem(view);
case CHILD:
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");
}
@@ -160,36 +284,38 @@ public class CombinedHistoryAdapter exte
if (inChildView) {
if (position == 0) {
return ItemType.itemTypeToViewType(ItemType.NAVIGATION_BACK);
}
return ItemType.itemTypeToViewType(ItemType.CHILD);
} else {
final int numClients = remoteClients.size();
if (position < numClients) {
+ if (!hiddenClients.isEmpty() && position == numClients - 1) {
+ return ItemType.itemTypeToViewType(ItemType.HIDDEN_DEVICES);
+ }
return ItemType.itemTypeToViewType(ItemType.CLIENT);
}
- final int sectionPosition = transformPosition(ItemType.SECTION_HEADER, position);
+ final int sectionPosition = transformAdapterPositionForDataStructure(ItemType.SECTION_HEADER, position);
if (sectionHeaders.get(sectionPosition) != null) {
return ItemType.itemTypeToViewType(ItemType.SECTION_HEADER);
}
return ItemType.itemTypeToViewType(ItemType.HISTORY);
}
}
@Override
public int getItemCount() {
if (inChildView) {
return (clientChildren == null) ? 0 : clientChildren.size();
} else {
- final int remoteSize = remoteClients.size();
final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
- return remoteSize + historySize + sectionHeaders.size();
+ return remoteClients.size() + historySize + sectionHeaders.size();
}
}
/**
* 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
@@ -224,35 +350,41 @@ public class CombinedHistoryAdapter exte
if (historyCursor == null) {
return false;
}
return (historyCursor.getCount() > 0);
}
@Override
public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
- final ItemType itemType = ItemType.viewTypeToItemType(getItemViewType(position));
- final int localPosition = transformPosition(itemType, position);
+ final ItemType itemType = getItemTypeForPosition(position);
+ final int localPosition = transformAdapterPositionForDataStructure(itemType, position);
switch (itemType) {
case CLIENT:
final CombinedHistoryItem.ClientItem clientItem = (CombinedHistoryItem.ClientItem) viewHolder;
final RemoteClient client = remoteClients.get(localPosition);
clientItem.bind(client, context);
break;
+ case HIDDEN_DEVICES:
+ final String hiddenDevicesLabel = context.getResources().getString(R.string.home_remote_tabs_many_hidden_devices, hiddenClients.size());
+ ((TextView) viewHolder.itemView).setText(hiddenDevicesLabel);
+ break;
+
case CHILD:
RemoteTab remoteTab = clientChildren.get(position);
((CombinedHistoryItem.HistoryItem) viewHolder).bind(remoteTab);
break;
case SECTION_HEADER:
((TextView) viewHolder.itemView).setText(CombinedHistoryPanel.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;
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
@@ -16,18 +16,18 @@ import org.mozilla.gecko.RemoteTabsExpan
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.RemoteTab;
public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
public CombinedHistoryItem(View view) {
super(view);
}
- public static class SectionItem extends CombinedHistoryItem {
- public SectionItem(View view) {
+ public static class BasicItem extends CombinedHistoryItem {
+ public BasicItem(View view) {
super(view);
}
}
public static class HistoryItem extends CombinedHistoryItem {
public HistoryItem(View view) {
super(view);
}
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
@@ -6,53 +6,59 @@
package org.mozilla.gecko.home;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.widget.DefaultItemAnimator;
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.view.ViewStub;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
+import org.mozilla.gecko.RemoteClientsDialogFragment;
import org.mozilla.gecko.Restrictions;
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.RemoteClient;
import org.mozilla.gecko.db.RemoteTab;
import org.mozilla.gecko.home.HistorySectionsHelper.SectionDateRange;
import org.mozilla.gecko.restrictions.Restrictable;
import org.mozilla.gecko.widget.DividerItemDecoration;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-public class CombinedHistoryPanel extends HomeFragment {
+public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsDialogFragment.RemoteClientsListener {
private static final String LOGTAG = "GeckoCombinedHistoryPnl";
private final int LOADER_ID_HISTORY = 0;
private final int LOADER_ID_REMOTE = 1;
// Semantic names for the time covered by each section
public enum SectionHeader {
TODAY,
YESTERDAY,
@@ -101,16 +107,17 @@ public class CombinedHistoryPanel extend
mRecyclerView = (CombinedHistoryRecyclerView) view.findViewById(R.id.combined_recycler_view);
mAdapter = new CombinedHistoryAdapter(getContext());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext()));
mRecyclerView.setOnHistoryClickedListener(mUrlOpenListener);
mRecyclerView.setOnPanelLevelChangeListener(new OnLevelChangeListener());
+ mRecyclerView.setHiddenClientsDialogBuilder(new HiddenClientsHelper());
registerForContextMenu(mRecyclerView);
mPanelFooterButton = (Button) view.findViewById(R.id.clear_history_button);
mPanelFooterButton.setOnClickListener(new OnFooterButtonClickListener());
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
@@ -165,17 +172,17 @@ public class CombinedHistoryPanel extend
if (time > sectionDateRangeArray[i].start) {
return SectionHeader.values()[i];
}
}
return SectionHeader.OLDER_THAN_SIX_MONTHS;
}
- private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks {
+ 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 = GeckoProfile.get(getActivity()).getDB();
}
@@ -185,34 +192,41 @@ public class CombinedHistoryPanel extend
case LOADER_ID_REMOTE:
return new RemoteTabsCursorLoader(getContext());
default:
Log.e(LOGTAG, "Unknown loader id!");
return null;
}
}
- protected void onLoadFinishedAfterTransitions(Loader<Cursor> loader, Cursor c) {
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
final int loaderId = loader.getId();
switch (loaderId) {
case LOADER_ID_HISTORY:
mAdapter.setHistory(c);
break;
case LOADER_ID_REMOTE:
final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
- // TODO: Handle hidden clients
+
mAdapter.setClients(clients);
break;
}
// Check and set empty state.
updateButtonFromLevel(OnPanelLevelChangeListener.PanelLevel.PARENT);
updateEmptyView(mAdapter.getItemCount() == 0);
}
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ mAdapter.setClients(Collections.<RemoteClient>emptyList());
+ mAdapter.setHistory(null);
+ }
}
protected class OnLevelChangeListener implements OnPanelLevelChangeListener {
@Override
public void onPanelLevelChange(PanelLevel level) {
updateButtonFromLevel(level);
}
}
@@ -257,32 +271,32 @@ public class CombinedHistoryPanel extend
// 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.sendEventToGecko(GeckoEvent.createBroadcastEvent("Sanitize:ClearData", json.toString()));
+ GeckoAppShell.notifyObservers("Sanitize:ClearData", json.toString());
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
}
});
dialogBuilder.show();
break;
case CHILD:
final JSONArray tabUrls = ((CombinedHistoryAdapter) mRecyclerView.getAdapter()).getCurrentChildTabs();
if (tabUrls != null) {
final JSONObject message = new JSONObject();
try {
message.put("urls", tabUrls);
message.put("shouldNotifyTabsOpenedToJava", false);
- GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tabs:OpenMultiple", message.toString()));
+ GeckoAppShell.notifyObservers("Tabs:OpenMultiple", message.toString());
} catch (JSONException e) {
Log.e(LOGTAG, "Error making JSON message to open tabs");
}
}
break;
}
}
}
@@ -373,16 +387,91 @@ public class CombinedHistoryPanel extend
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) {
+ ((CombinedHistoryAdapter) mRecyclerView.getAdapter()).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) {
+ ((CombinedHistoryAdapter) mRecyclerView.getAdapter()).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 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
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
@@ -5,29 +5,31 @@
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.View;
+import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener;
import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
import java.util.EnumSet;
public class CombinedHistoryRecyclerView extends RecyclerView
implements RecyclerViewClickSupport.OnItemClickListener, RecyclerViewClickSupport.OnItemLongClickListener {
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) {
@@ -53,26 +55,36 @@ public class CombinedHistoryRecyclerView
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 CombinedHistoryAdapter.ItemType itemType = CombinedHistoryAdapter.ItemType.viewTypeToItemType(viewType);
switch(itemType) {
case CLIENT:
mOnPanelLevelChangeListener.onPanelLevelChange(PanelLevel.CHILD);
((CombinedHistoryAdapter) getAdapter()).showChildView(position);
break;
+ case HIDDEN_DEVICES:
+ if (mDialogBuilder != null) {
+ mDialogBuilder.createAndShowDialog(((CombinedHistoryAdapter) getAdapter()).getHiddenClients());
+ }
+ break;
+
case NAVIGATION_BACK:
mOnPanelLevelChangeListener.onPanelLevelChange(PanelLevel.PARENT);
((CombinedHistoryAdapter) getAdapter()).exitChildView();
break;
case CHILD:
case HISTORY:
if (mOnUrlOpenListener != null) {
final TwoLinePageRow historyItem = (TwoLinePageRow) v;
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsBaseFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsBaseFragment.java
@@ -137,23 +137,16 @@ public abstract class RemoteTabsBaseFrag
}
// 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);
-
- // Hide unused menu items.
- final boolean isHidden = sState.isClientHidden(info.client.guid);
- final MenuItem item = menu.findItem(isHidden
- ? R.id.home_remote_tabs_hide_client
- : R.id.home_remote_tabs_show_client);
- item.setVisible(false);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (super.onContextItemSelected(item)) {
// HomeFragment was able to handle to selected item.
return true;
}
@@ -167,22 +160,16 @@ public abstract class RemoteTabsBaseFrag
final int itemId = item.getItemId();
if (itemId == R.id.home_remote_tabs_hide_client) {
sState.setClientHidden(info.client.guid, true);
getLoaderManager().restartLoader(LOADER_ID_REMOTE_TABS, null, mCursorLoaderCallbacks);
return true;
}
- if (itemId == R.id.home_remote_tabs_show_client) {
- sState.setClientHidden(info.client.guid, false);
- getLoaderManager().restartLoader(LOADER_ID_REMOTE_TABS, null, mCursorLoaderCallbacks);
- return true;
- }
-
return false;
}
@Override
public void onClients(List<RemoteClient> clients) {
// The clients listed were hidden and have been checked by the user. We
// interpret that as "show these clients now".
for (RemoteClient client : clients) {
--- a/mobile/android/base/resources/drawable/action_bar_button.xml
+++ b/mobile/android/base/resources/drawable/action_bar_button.xml
@@ -14,11 +14,11 @@
<item android:state_focused="true"
android:state_pressed="false">
<shape>
<solid android:color="@color/highlight_focused" />
</shape>
</item>
- <item android:drawable="@android:color/transparent"/>
+ <item android:drawable="@color/about_page_header_grey"/>
</selector>
copy from mobile/android/base/resources/layout/home_remote_tabs_hidden_devices_footer.xml
copy to mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml
--- a/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices_footer.xml
+++ b/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml
@@ -1,28 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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/.
-->
-<!-- This layout is actually necessary because of an interaction
- between ExpandableListView and onClick handling. We need a child
- to attach a click listener to. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- style="@style/Widget.RemoteTabsClientView"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/hidden_devices"
+ style="@style/Widget.Home.ActionItem"
+ android:background="@drawable/action_bar_button"
android:layout_width="match_parent"
android:layout_height="@dimen/home_remote_tabs_hidden_footer_height"
- android:gravity="center_vertical" >
-
- <TextView
- android:id="@+id/hidden_devices"
- style="@style/Widget.Home.ActionItem"
- android:background="@drawable/action_bar_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:maxLength="1024"
- android:textColor="@color/tabs_tray_icon_grey" />
-
-</LinearLayout>
+ android:gravity="center"
+ android:maxLength="1024"
+ android:textColor="@color/tabs_tray_icon_grey" />
--- a/mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml
+++ b/mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml
@@ -3,12 +3,9 @@
- 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/. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/home_remote_tabs_hide_client"
android:title="@string/pref_panels_hide"/>
- <item android:id="@+id/home_remote_tabs_show_client"
- android:title="@string/pref_panels_show"/>
-
</menu>