Suppress auto-closing drawer and add ripple effect on spring load roots.
Bug: 28865182
Change-Id: Ief7967e33b9a0d7e94a667172121d8007f78115b
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index 3597a74..d1285c8 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -256,8 +256,6 @@
} else {
new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
}
-
- mNavigator.revealRootsDrawer(false);
}
@Override
diff --git a/src/com/android/documentsui/ItemDragListener.java b/src/com/android/documentsui/ItemDragListener.java
index 2c018f8..66b94fc 100644
--- a/src/com/android/documentsui/ItemDragListener.java
+++ b/src/com/android/documentsui/ItemDragListener.java
@@ -17,6 +17,7 @@
package com.android.documentsui;
import android.content.ClipData;
+import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
@@ -62,6 +63,7 @@
handleEnteredEvent(v);
return true;
case DragEvent.ACTION_DRAG_LOCATION:
+ handleLocationEvent(v, event.getX(), event.getY());
return true;
case DragEvent.ACTION_DRAG_EXITED:
case DragEvent.ACTION_DRAG_ENDED:
@@ -83,6 +85,13 @@
mHoverTimer.schedule(task, ViewConfiguration.getLongPressTimeout());
}
+ private void handleLocationEvent(View v, float x, float y) {
+ Drawable background = v.getBackground();
+ if (background != null) {
+ background.setHotspot(x, y);
+ }
+ }
+
private void handleExitedEndedEvent(View v) {
mDragHost.setDropTargetHighlight(v, false);
diff --git a/src/com/android/documentsui/RootItemView.java b/src/com/android/documentsui/RootItemView.java
new file mode 100644
index 0000000..93aa526
--- /dev/null
+++ b/src/com/android/documentsui/RootItemView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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 com.android.documentsui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+public final class RootItemView extends LinearLayout {
+ private static final int[] STATE_HIGHLIGHTED = {R.attr.state_highlighted};
+
+ private boolean mHighlighted = false;
+
+ public RootItemView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+ if (mHighlighted) {
+ mergeDrawableStates(drawableState, STATE_HIGHLIGHTED);
+ }
+
+ return drawableState;
+ }
+
+ public void setHighlight(boolean highlight) {
+ mHighlighted = highlight;
+ refreshDrawableState();
+ }
+
+ /**
+ * Synthesizes pressed state to trick RippleDrawable starting a ripple effect.
+ */
+ public void drawRipple() {
+ setPressed(true);
+ setPressed(false);
+ }
+}
diff --git a/src/com/android/documentsui/RootsFragment.java b/src/com/android/documentsui/RootsFragment.java
index ad2ee07..b333379 100644
--- a/src/com/android/documentsui/RootsFragment.java
+++ b/src/com/android/documentsui/RootsFragment.java
@@ -18,6 +18,7 @@
import static com.android.documentsui.Shared.DEBUG;
+import android.annotation.LayoutRes;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
@@ -26,12 +27,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Looper;
import android.provider.Settings;
-import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.text.format.Formatter;
@@ -54,7 +56,9 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -198,6 +202,10 @@
*/
@Override
public void onViewHovered(View view) {
+ // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
+ RootItemView itemView = (RootItemView) view;
+ itemView.drawRipple();
+
final int position = (Integer) view.getTag(R.id.item_position_tag);
final Item item = mAdapter.getItem(position);
item.open(this);
@@ -205,10 +213,9 @@
@Override
public void setDropTargetHighlight(View v, boolean highlight) {
- @ColorRes int colorId = highlight ? R.color.item_doc_background_selected
- : android.R.color.transparent;
-
- v.setBackgroundColor(getActivity().getColor(colorId));
+ // SpacerView doesn't have DragListener so this view is guaranteed to be a RootItemView.
+ RootItemView itemView = (RootItemView) v;
+ itemView.setHighlight(highlight);
}
private OnItemClickListener mItemListener = new OnItemClickListener() {
@@ -216,6 +223,8 @@
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Item item = mAdapter.getItem(position);
item.open(RootsFragment.this);
+
+ ((BaseActivity) getActivity()).setRootsDrawerOpen(false);
}
};
@@ -223,32 +232,34 @@
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
final Item item = mAdapter.getItem(position);
- if (item instanceof AppItem) {
- showAppDetails(((AppItem) item).info);
- return true;
- } else {
- return false;
- }
+ return item.showAppDetails(RootsFragment.this);
}
};
private static abstract class Item {
- private final int mLayoutId;
+ private final @LayoutRes int mLayoutId;
+ private final String mStringId;
- public Item(int layoutId) {
+ public Item(@LayoutRes int layoutId, String stringId) {
mLayoutId = layoutId;
+ mStringId = stringId;
}
public View getView(View convertView, ViewGroup parent) {
- // Disable recycling views because 1) it's very unlikely a view can be recycled here;
- // 2) there is no easy way for us to know with which layout id the convertView was
- // inflated; and 3) simplicity is much appreciated at this time.
- convertView = LayoutInflater.from(parent.getContext())
+ if (convertView == null
+ || (Integer) convertView.getTag(R.id.layout_id_tag) != mLayoutId) {
+ convertView = LayoutInflater.from(parent.getContext())
.inflate(mLayoutId, parent, false);
+ }
+ convertView.setTag(R.id.layout_id_tag, mLayoutId);
bindView(convertView);
return convertView;
}
+ boolean showAppDetails(RootsFragment fragment) {
+ return false;
+ }
+
abstract void bindView(View convertView);
abstract boolean isDropTarget();
@@ -257,13 +268,23 @@
}
private static class RootItem extends Item {
+ private static final String STRING_ID_FORMAT = "RootItem{%s/%s}";
+
public final RootInfo root;
public RootItem(RootInfo root) {
- super(R.layout.item_root);
+ super(R.layout.item_root, getStringId(root));
this.root = root;
}
+ private static String getStringId(RootInfo root) {
+ // Empty URI authority is invalid, so we can use empty string if root.authority is null.
+ // Directly passing null to String.format() will write "null" which can be a valid URI
+ // authority.
+ String authority = (root.authority == null ? "" : root.authority);
+ return String.format(STRING_ID_FORMAT, authority, root.rootId);
+ }
+
@Override
public void bindView(View convertView) {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
@@ -291,7 +312,7 @@
}
@Override
- public void open(RootsFragment fragment) {
+ void open(RootsFragment fragment) {
BaseActivity activity = BaseActivity.get(fragment);
Metrics.logRootVisited(fragment.getActivity(), root);
activity.onRootPicked(root);
@@ -299,8 +320,11 @@
}
private static class SpacerItem extends Item {
+ private static final String STRING_ID = "SpacerItem";
+
public SpacerItem() {
- super(R.layout.item_root_spacer);
+ // Multiple spacer items can share the same string id as they're identical.
+ super(R.layout.item_root_spacer, STRING_ID);
}
@Override
@@ -314,19 +338,35 @@
}
@Override
- public void open(RootsFragment fragment) {
+ void open(RootsFragment fragment) {
if (DEBUG) Log.d(TAG, "Ignoring click/hover on spacer item.");
}
}
private static class AppItem extends Item {
+ private static final String STRING_ID_FORMAT = "AppItem{%s/%s}";
+
public final ResolveInfo info;
public AppItem(ResolveInfo info) {
- super(R.layout.item_root);
+ super(R.layout.item_root, getStringId(info));
this.info = info;
}
+ private static String getStringId(ResolveInfo info) {
+ ActivityInfo activityInfo = info.activityInfo;
+
+ String component = String.format(
+ STRING_ID_FORMAT, activityInfo.applicationInfo.packageName, activityInfo.name);
+ return component;
+ }
+
+ @Override
+ boolean showAppDetails(RootsFragment fragment) {
+ fragment.showAppDetails(info);
+ return true;
+ }
+
@Override
void bindView(View convertView) {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
@@ -348,7 +388,7 @@
}
@Override
- public void open(RootsFragment fragment) {
+ void open(RootsFragment fragment) {
DocumentsActivity activity = DocumentsActivity.get(fragment);
Metrics.logAppVisited(fragment.getActivity(), info);
activity.onAppPicked(info);
@@ -356,6 +396,9 @@
}
private static class RootsAdapter extends ArrayAdapter<Item> {
+ private static final Map<String, Long> sIdMap = new HashMap<String, Long>();
+ // the next available id to associate with a new string id
+ private static long sNextAvailableId;
private OnDragListener mDragListener;
@@ -430,6 +473,30 @@
}
@Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Ensure this method is only called in main thread because we don't have any
+ // concurrency protection.
+ assert(Looper.myLooper() == Looper.getMainLooper());
+
+ String stringId = getItem(position).mStringId;
+
+ long id;
+ if (sIdMap.containsKey(stringId)) {
+ id = sIdMap.get(stringId);
+ } else {
+ id = sNextAvailableId++;
+ sIdMap.put(stringId, id);
+ }
+
+ return id;
+ }
+
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
final Item item = getItem(position);
final View view = item.getView(convertView, parent);