diff options
Diffstat (limited to 'packages')
28 files changed, 1010 insertions, 474 deletions
diff --git a/packages/DocumentsUI/res/drawable/item_doc_grid.xml b/packages/DocumentsUI/res/drawable/item_doc_grid.xml new file mode 100644 index 0000000..3f036f7 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_doc_grid.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/ic_grid_card_background" /> +</selector> diff --git a/packages/DocumentsUI/res/drawable/item_root.xml b/packages/DocumentsUI/res/drawable/item_root.xml new file mode 100644 index 0000000..183d273 --- /dev/null +++ b/packages/DocumentsUI/res/drawable/item_root.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:drawable="@color/item_root_activated" /> + <item android:state_activated="true" android:drawable="@color/item_root_activated" /> + <item android:state_focused="true" android:drawable="@color/item_root_activated" /> + <item android:drawable="@android:color/white" /> +</selector> diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index 67c5954..881349b 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -38,8 +38,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:listSelector="@android:color/transparent" - android:paddingTop="?android:attr/listPreferredItemPaddingStart" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:visibility="gone" /> <Button diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml index 244214b..8d1fc9a 100644 --- a/packages/DocumentsUI/res/layout/item_doc_grid.xml +++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml @@ -16,110 +16,111 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="180dip" - android:paddingBottom="?android:attr/listPreferredItemPaddingEnd" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:layout_height="@dimen/grid_height" + android:background="@drawable/item_doc_grid" + android:foreground="@drawable/item_background"> - <FrameLayout + <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/chip" - android:foreground="@drawable/item_background" - android:duplicateParentState="true"> + android:paddingBottom="6dp" + android:orientation="vertical"> - <LinearLayout + <FrameLayout android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="6dp" - android:orientation="vertical"> + android:layout_height="0dip" + android:layout_weight="1" + android:background="#fff"> <ImageView android:id="@android:id/icon" android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" - android:background="#bbb" + android:layout_height="match_parent" android:scaleType="centerInside" android:contentDescription="@null" /> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitXY" + android:src="@drawable/ic_grid_gradient_bg" + android:contentDescription="@null" /> + + </FrameLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingTop="6dp" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + <TextView android:id="@android:id/title" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_weight="1" android:singleLine="true" android:ellipsize="marquee" - android:paddingTop="6dp" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:textAppearance="?android:attr/textAppearanceSmall" + android:textAppearance="?android:attr/textAppearanceMedium" android:textAlignment="viewStart" /> - <LinearLayout - android:id="@+id/summary_grid" - android:layout_width="match_parent" + <ImageView + android:id="@android:id/icon1" + android:layout_width="@dimen/root_icon_size" + android:layout_height="@dimen/root_icon_size" + android:layout_marginStart="8dip" + android:scaleType="centerInside" + android:contentDescription="@null" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/line2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <ImageView - android:id="@android:id/icon1" - android:layout_width="24dip" - android:layout_height="24dip" - android:layout_marginEnd="6dip" - android:scaleType="centerInside" - android:contentDescription="@null" /> - - <TextView - android:id="@android:id/summary" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - android:singleLine="true" - android:ellipsize="marquee" - android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" + android:layout_gravity="center_vertical" + android:minWidth="80dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:id="@+id/size" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <View - android:layout_width="0dip" - android:layout_height="0dip" - android:layout_weight="1" /> - - <TextView - android:id="@+id/size" - android:layout_width="70dp" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp" - android:singleLine="true" - android:ellipsize="marquee" - android:textAlignment="viewEnd" - android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView - android:id="@+id/date" - android:layout_width="70dp" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:singleLine="true" - android:ellipsize="marquee" - android:textAlignment="viewEnd" - android:textAppearance="?android:attr/textAppearanceSmall" /> - - </LinearLayout> + android:layout_gravity="center_vertical" + android:layout_marginStart="8dp" + android:minWidth="80dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceSmall" /> + + <TextView + android:id="@android:id/summary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="center_vertical" + android:layout_marginStart="8dp" + android:singleLine="true" + android:ellipsize="marquee" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> - </FrameLayout> + </LinearLayout> </FrameLayout> diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index 37c5881..8372eed 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -27,9 +27,10 @@ <ImageView android:id="@android:id/icon" - android:layout_width="@android:dimen/app_icon_size" - android:layout_height="@android:dimen/app_icon_size" - android:layout_marginEnd="8dip" + android:layout_width="@dimen/icon_size" + android:layout_height="@dimen/icon_size" + android:layout_marginStart="12dp" + android:layout_marginEnd="20dp" android:layout_gravity="center_vertical" android:scaleType="centerInside" android:contentDescription="@null" /> @@ -41,36 +42,43 @@ android:layout_gravity="center_vertical" android:orientation="vertical"> - <TextView - android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> - <LinearLayout - android:id="@+id/summary_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> + <TextView + android:id="@android:id/title" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textAlignment="viewStart" /> + <ImageView android:id="@android:id/icon1" - android:layout_width="24dip" - android:layout_height="24dip" - android:layout_marginEnd="6dip" + android:layout_width="@dimen/root_icon_size" + android:layout_height="@dimen/root_icon_size" + android:layout_marginStart="8dip" android:scaleType="centerInside" android:contentDescription="@null" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/line2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + <TextView - android:id="@android:id/summary" - android:layout_width="0dp" + android:id="@+id/date" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp" + android:minWidth="70dp" android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" @@ -78,23 +86,26 @@ <TextView android:id="@+id/size" - android:layout_width="70dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginEnd="8dp" + android:minWidth="70dp" + android:layout_marginStart="8dp" android:singleLine="true" android:ellipsize="marquee" - android:textAlignment="viewEnd" + android:textAlignment="viewStart" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView - android:id="@+id/date" - android:layout_width="70dp" + android:id="@android:id/summary" + android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_weight="1" android:layout_gravity="center_vertical" + android:layout_marginStart="8dp" android:singleLine="true" android:ellipsize="marquee" - android:textAlignment="viewEnd" + android:textAlignment="viewStart" android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml index e9cf3aa..ce97b57 100644 --- a/packages/DocumentsUI/res/layout/item_root.xml +++ b/packages/DocumentsUI/res/layout/item_root.xml @@ -17,17 +17,17 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" + android:minHeight="?android:attr/listPreferredItemHeightSmall" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:gravity="center_vertical" - android:orientation="horizontal"> + android:orientation="horizontal" + android:background="@drawable/item_root"> <ImageView android:id="@android:id/icon" - android:layout_width="@android:dimen/app_icon_size" - android:layout_height="@android:dimen/app_icon_size" - android:layout_rowSpan="2" + android:layout_width="@dimen/icon_size" + android:layout_height="@dimen/icon_size" android:layout_marginEnd="8dip" android:scaleType="centerInside" android:contentDescription="@null" /> diff --git a/packages/DocumentsUI/res/layout/item_root_header.xml b/packages/DocumentsUI/res/layout/item_root_header.xml index 2b9a46f..127b254 100644 --- a/packages/DocumentsUI/res/layout/item_root_header.xml +++ b/packages/DocumentsUI/res/layout/item_root_header.xml @@ -16,14 +16,4 @@ <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingTop="8dp" - android:paddingBottom="8dp" - android:singleLine="true" - android:ellipsize="marquee" - android:textAllCaps="true" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="viewStart" /> + style="?android:attr/listSeparatorTextViewStyle" /> diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml index eab3839..9594e4e 100644 --- a/packages/DocumentsUI/res/layout/item_title.xml +++ b/packages/DocumentsUI/res/layout/item_title.xml @@ -17,8 +17,20 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:gravity="center_vertical" - android:orientation="vertical"> + android:orientation="horizontal"> + + <ImageView + android:id="@+id/subdir" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleType="centerInside" + android:visibility="gone" + android:src="@drawable/ic_subdirectory_arrow" + android:contentDescription="@null" /> <TextView android:id="@android:id/title" diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml index ff3e999..6d62759 100644 --- a/packages/DocumentsUI/res/values/colors.xml +++ b/packages/DocumentsUI/res/values/colors.xml @@ -16,4 +16,5 @@ <resources> <color name="chip">#ddd</color> + <color name="item_root_activated">#cccccc</color> </resources> diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml index e5c4138..e5b5b4e 100644 --- a/packages/DocumentsUI/res/values/dimens.xml +++ b/packages/DocumentsUI/res/values/dimens.xml @@ -15,5 +15,8 @@ --> <resources> + <dimen name="icon_size">32dp</dimen> + <dimen name="root_icon_size">24dp</dimen> <dimen name="grid_width">180dp</dimen> + <dimen name="grid_height">180dp</dimen> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index e0b8d19..d8e60aa 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -70,7 +70,7 @@ public class CreateDirectoryFragment extends DialogFragment { try { final Uri childUri = DocumentsContract.createDocument( - resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName); + resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName); // Navigate into newly created child final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index ba464f7..a13beba 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -20,6 +20,8 @@ import static com.android.documentsui.DocumentsActivity.TAG; import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE; import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; import static com.android.documentsui.model.DocumentInfo.getCursorInt; import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; @@ -91,43 +93,42 @@ public class DirectoryFragment extends Fragment { private int mType = TYPE_NORMAL; + private int mLastMode = MODE_UNKNOWN; + private int mLastSortOrder = SORT_ORDER_UNKNOWN; + private Point mThumbSize; private DocumentsAdapter mAdapter; private LoaderCallbacks<DirectoryResult> mCallbacks; private static final String EXTRA_TYPE = "type"; - private static final String EXTRA_AUTHORITY = "authority"; - private static final String EXTRA_ROOT_ID = "rootId"; - private static final String EXTRA_DOC_ID = "docId"; + private static final String EXTRA_ROOT = "root"; + private static final String EXTRA_DOC = "doc"; private static final String EXTRA_QUERY = "query"; private static AtomicInteger sLoaderId = new AtomicInteger(4000); - private int mLastSortOrder = -1; - private final int mLoaderId = sLoaderId.incrementAndGet(); - public static void showNormal(FragmentManager fm, Uri uri) { - show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null); + public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) { + show(fm, TYPE_NORMAL, root, doc, null); } - public static void showSearch(FragmentManager fm, Uri uri, String query) { - show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), - query); + public static void showSearch( + FragmentManager fm, RootInfo root, DocumentInfo doc, String query) { + show(fm, TYPE_SEARCH, root, doc, query); } public static void showRecentsOpen(FragmentManager fm) { - show(fm, TYPE_RECENT_OPEN, null, null, null, null); + show(fm, TYPE_RECENT_OPEN, null, null, null); } - private static void show(FragmentManager fm, int type, String authority, String rootId, - String docId, String query) { + private static void show( + FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) { final Bundle args = new Bundle(); args.putInt(EXTRA_TYPE, type); - args.putString(EXTRA_AUTHORITY, authority); - args.putString(EXTRA_ROOT_ID, rootId); - args.putString(EXTRA_DOC_ID, docId); + args.putParcelable(EXTRA_ROOT, root); + args.putParcelable(EXTRA_DOC, doc); args.putString(EXTRA_QUERY, query); final DirectoryFragment fragment = new DirectoryFragment(); @@ -167,6 +168,7 @@ public class DirectoryFragment extends Fragment { super.onActivityCreated(savedInstanceState); final Context context = getActivity(); + final State state = getDisplayState(DirectoryFragment.this); mAdapter = new DocumentsAdapter(); mType = getArguments().getInt(EXTRA_TYPE); @@ -174,35 +176,48 @@ public class DirectoryFragment extends Fragment { mCallbacks = new LoaderCallbacks<DirectoryResult>() { @Override public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { - final State state = getDisplayState(DirectoryFragment.this); - - final String authority = getArguments().getString(EXTRA_AUTHORITY); - final String rootId = getArguments().getString(EXTRA_ROOT_ID); - final String docId = getArguments().getString(EXTRA_DOC_ID); + final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); + final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); final String query = getArguments().getString(EXTRA_QUERY); Uri contentsUri; switch (mType) { case TYPE_NORMAL: - contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId); - return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder); + contentsUri = DocumentsContract.buildChildDocumentsUri( + doc.authority, doc.documentId); + return new DirectoryLoader(context, root, doc, contentsUri); case TYPE_SEARCH: contentsUri = DocumentsContract.buildSearchDocumentsUri( - authority, docId, query); - return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder); + doc.authority, doc.documentId, query); + return new DirectoryLoader(context, root, doc, contentsUri); case TYPE_RECENT_OPEN: final RootsCache roots = DocumentsApplication.getRootsCache(context); final List<RootInfo> matchingRoots = roots.getMatchingRoots(state); - return new RecentLoader(context, matchingRoots); + return new RecentLoader(context, matchingRoots, state.acceptMimes); default: throw new IllegalStateException("Unknown type " + mType); - } } @Override public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) { + if (!isAdded()) return; + mAdapter.swapCursor(result.cursor); + + // Push latest state up to UI + // TODO: if mode change was racing with us, don't overwrite it + state.mode = result.mode; + state.sortOrder = result.sortOrder; + ((DocumentsActivity) context).onStateChanged(); + + updateDisplayState(); + + if (mLastSortOrder != result.sortOrder) { + mLastSortOrder = result.sortOrder; + mListView.smoothScrollToPosition(0); + mGridView.smoothScrollToPosition(0); + } } @Override @@ -211,25 +226,39 @@ public class DirectoryFragment extends Fragment { } }; + // Kick off loader at least once + getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); + + updateDisplayState(); + } + + @Override + public void onStart() { + super.onStart(); updateDisplayState(); } - public void updateDisplayState() { + public void onUserSortOrderChanged() { + // User change always triggers reload + getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); + } + + public void onUserModeChanged() { + // Mode change is just display; no need to reload + updateDisplayState(); + } + + private void updateDisplayState() { final State state = getDisplayState(this); - if (mLastSortOrder != state.sortOrder) { - getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); - mLastSortOrder = state.sortOrder; - } + mFilter = new MimePredicate(state.acceptMimes); - mListView.smoothScrollToPosition(0); - mGridView.smoothScrollToPosition(0); + if (mLastMode == state.mode) return; + mLastMode = state.mode; mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE); mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE); - mFilter = new MimePredicate(state.acceptMimes); - final int choiceMode; if (state.allowMultiple) { choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; @@ -248,14 +277,14 @@ public class DirectoryFragment extends Fragment { mGridView.setChoiceMode(choiceMode); mCurrentView = mGridView; } else if (state.mode == MODE_LIST) { - thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); + thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size); mGridView.setAdapter(null); mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE); mListView.setAdapter(mAdapter); mListView.setChoiceMode(choiceMode); mCurrentView = mListView; } else { - throw new IllegalStateException(); + throw new IllegalStateException("Unknown state " + state.mode); } mThumbSize = new Point(thumbSize, thumbSize); @@ -360,7 +389,7 @@ public class DirectoryFragment extends Fragment { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setType(doc.mimeType); - intent.putExtra(Intent.EXTRA_STREAM, doc.uri); + intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri); } else if (docs.size() > 1) { intent = new Intent(Intent.ACTION_SEND_MULTIPLE); @@ -371,7 +400,7 @@ public class DirectoryFragment extends Fragment { final ArrayList<Uri> uris = Lists.newArrayList(); for (DocumentInfo doc : docs) { mimeTypes.add(doc.mimeType); - uris.add(doc.uri); + uris.add(doc.derivedUri); } intent.setType(findCommonMimeType(mimeTypes)); @@ -397,7 +426,7 @@ public class DirectoryFragment extends Fragment { continue; } - if (!DocumentsContract.deleteDocument(resolver, doc.uri)) { + if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) { Log.w(TAG, "Failed to delete " + doc); hadTrouble = true; } @@ -541,7 +570,7 @@ public class DirectoryFragment extends Fragment { final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); final TextView title = (TextView) convertView.findViewById(android.R.id.title); - final View summaryGrid = convertView.findViewById(R.id.summary_grid); + final View line2 = convertView.findViewById(R.id.line2); final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); final TextView date = (TextView) convertView.findViewById(R.id.date); @@ -571,31 +600,32 @@ public class DirectoryFragment extends Fragment { title.setText(docDisplayName); + boolean hasLine2 = false; + if (mType == TYPE_RECENT_OPEN) { final RootInfo root = roots.getRoot(docAuthority, docRootId); icon1.setVisibility(View.VISIBLE); icon1.setImageDrawable(root.loadIcon(context)); summary.setText(root.getDirectoryString()); summary.setVisibility(View.VISIBLE); + summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END); + hasLine2 = true; } else { icon1.setVisibility(View.GONE); if (docSummary != null) { summary.setText(docSummary); summary.setVisibility(View.VISIBLE); + hasLine2 = true; } else { summary.setVisibility(View.INVISIBLE); } } - if (summaryGrid != null) { - summaryGrid.setVisibility( - (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE); - } - if (docLastModified == -1) { date.setText(null); } else { date.setText(formatTime(context, docLastModified)); + hasLine2 = true; } if (state.showSize) { @@ -604,11 +634,14 @@ public class DirectoryFragment extends Fragment { size.setText(null); } else { size.setText(Formatter.formatFileSize(context, docSize)); + hasLine2 = true; } } else { size.setVisibility(View.GONE); } + line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE); + return convertView; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 6ea57d7..72dfa30 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -16,18 +16,29 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN; +import static com.android.documentsui.model.DocumentInfo.getCursorInt; import android.content.AsyncTaskLoader; import android.content.ContentProviderClient; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.provider.DocumentsContract.Document; +import android.util.Log; + +import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.RecentsProvider.StateColumns; +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.RootInfo; import libcore.io.IoUtils; @@ -36,6 +47,9 @@ class DirectoryResult implements AutoCloseable { Cursor cursor; Exception exception; + int mode = MODE_UNKNOWN; + int sortOrder = SORT_ORDER_UNKNOWN; + @Override public void close() { IoUtils.closeQuietly(cursor); @@ -48,18 +62,18 @@ class DirectoryResult implements AutoCloseable { public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); - private final String mRootId; + private final RootInfo mRoot; + private final DocumentInfo mDoc; private final Uri mUri; - private final int mSortOrder; private CancellationSignal mSignal; private DirectoryResult mResult; - public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) { + public DirectoryLoader(Context context, RootInfo root, DocumentInfo doc, Uri uri) { super(context); - mRootId = rootId; + mRoot = root; + mDoc = doc; mUri = uri; - mSortOrder = sortOrder; } @Override @@ -70,20 +84,65 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } mSignal = new CancellationSignal(); } - final DirectoryResult result = new DirectoryResult(); + + final ContentResolver resolver = getContext().getContentResolver(); final String authority = mUri.getAuthority(); + + final DirectoryResult result = new DirectoryResult(); + + int userMode = State.MODE_UNKNOWN; + int userSortOrder = State.SORT_ORDER_UNKNOWN; + + // Pick up any custom modes requested by user + Cursor cursor = null; try { - result.client = getContext() - .getContentResolver().acquireUnstableContentProviderClient(authority); - final Cursor cursor = result.client.query( - mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal); + final Uri stateUri = RecentsProvider.buildState( + mRoot.authority, mRoot.rootId, mDoc.documentId); + cursor = resolver.query(stateUri, null, null, null, null); + if (cursor.moveToFirst()) { + userMode = getCursorInt(cursor, StateColumns.MODE); + userSortOrder = getCursorInt(cursor, StateColumns.SORT_ORDER); + } + } finally { + IoUtils.closeQuietly(cursor); + } + + if (userMode != State.MODE_UNKNOWN) { + result.mode = userMode; + } else { + if ((mDoc.flags & Document.FLAG_DIR_PREFERS_GRID) != 0) { + result.mode = State.MODE_GRID; + } else { + result.mode = State.MODE_LIST; + } + } + + if (userSortOrder != State.SORT_ORDER_UNKNOWN) { + result.sortOrder = userSortOrder; + } else { + if ((mDoc.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0) { + result.sortOrder = State.SORT_ORDER_LAST_MODIFIED; + } else { + result.sortOrder = State.SORT_ORDER_DISPLAY_NAME; + } + } + + Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + userSortOrder + " --> mode=" + + result.mode + ", sortOrder=" + result.sortOrder); + + try { + result.client = resolver.acquireUnstableContentProviderClient(authority); + cursor = result.client.query( + mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal); cursor.registerContentObserver(mObserver); - final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1); - final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder); + final Cursor withRoot = new RootCursorWrapper( + mUri.getAuthority(), mRoot.rootId, cursor, -1); + final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder); result.cursor = sorted; } catch (Exception e) { + Log.d(TAG, "Failed to query", e); result.exception = e; ContentProviderClient.closeQuietly(result.client); } finally { @@ -91,6 +150,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { mSignal = null; } } + return result; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index e1f2606..fe39800 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -53,12 +53,16 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.SearchView; import android.widget.SearchView.OnCloseListener; import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; import android.widget.Toast; +import com.android.documentsui.RecentsProvider.RecentColumns; +import com.android.documentsui.RecentsProvider.ResumeColumns; +import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.DurableUtils; @@ -190,7 +194,7 @@ public class DocumentsActivity extends Activity { try { if (cursor.moveToFirst()) { final byte[] rawStack = cursor.getBlob( - cursor.getColumnIndex(RecentsProvider.COL_PATH)); + cursor.getColumnIndex(ResumeColumns.STACK)); DurableUtils.readFromArray(rawStack, mState.stack); } } catch (IOException e) { @@ -203,7 +207,7 @@ public class DocumentsActivity extends Activity { final RootInfo root = getCurrentRoot(); final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState); if (!matchingRoots.contains(root)) { - mState.stack.clear(); + mState.stack.reset(); } // Only open drawer when showing recents @@ -342,11 +346,16 @@ public class DocumentsActivity extends Activity { final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem settings = menu.findItem(R.id.menu_settings); - grid.setVisible(mState.mode != MODE_GRID); - list.setVisible(mState.mode != MODE_LIST); + if (cwd != null) { + sort.setVisible(true); + grid.setVisible(mState.mode != MODE_GRID); + list.setVisible(mState.mode != MODE_LIST); + } else { + sort.setVisible(false); + grid.setVisible(false); + list.setVisible(false); + } - // No sorting in recents - sort.setVisible(cwd != null); // Only sort by size when visible sortSize.setVisible(mState.showSize); @@ -391,28 +400,19 @@ public class DocumentsActivity extends Activity { } else if (id == R.id.menu_search) { return false; } else if (id == R.id.menu_sort_name) { - mState.sortOrder = State.SORT_ORDER_DISPLAY_NAME; - updateDisplayState(); + setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME); return true; } else if (id == R.id.menu_sort_date) { - mState.sortOrder = State.SORT_ORDER_LAST_MODIFIED; - updateDisplayState(); + setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED); return true; } else if (id == R.id.menu_sort_size) { - mState.sortOrder = State.SORT_ORDER_SIZE; - updateDisplayState(); + setUserSortOrder(State.SORT_ORDER_SIZE); return true; } else if (id == R.id.menu_grid) { - // TODO: persist explicit user mode for cwd - mState.mode = MODE_GRID; - updateDisplayState(); - invalidateOptionsMenu(); + setUserMode(State.MODE_GRID); return true; } else if (id == R.id.menu_list) { - // TODO: persist explicit user mode for cwd - mState.mode = MODE_LIST; - updateDisplayState(); - invalidateOptionsMenu(); + setUserMode(State.MODE_LIST); return true; } else if (id == R.id.menu_settings) { startActivity(new Intent(this, SettingsActivity.class)); @@ -422,6 +422,51 @@ public class DocumentsActivity extends Activity { } } + /** + * Update UI to reflect internal state changes not from user. + */ + public void onStateChanged() { + invalidateOptionsMenu(); + } + + /** + * Set state sort order based on explicit user action. + */ + private void setUserSortOrder(int sortOrder) { + final RootInfo root = getCurrentRoot(); + final DocumentInfo cwd = getCurrentDirectory(); + + // TODO: persist async, then trigger rebind + final Uri stateUri = RecentsProvider.buildState( + root.authority, root.rootId, cwd.documentId); + final ContentValues values = new ContentValues(); + values.put(StateColumns.SORT_ORDER, sortOrder); + getContentResolver().insert(stateUri, values); + + DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged(); + onStateChanged(); + } + + /** + * Set state mode based on explicit user action. + */ + private void setUserMode(int mode) { + final RootInfo root = getCurrentRoot(); + final DocumentInfo cwd = getCurrentDirectory(); + + // TODO: persist async, then trigger rebind + final Uri stateUri = RecentsProvider.buildState( + root.authority, root.rootId, cwd.documentId); + final ContentValues values = new ContentValues(); + values.put(StateColumns.MODE, mode); + getContentResolver().insert(stateUri, values); + + mState.mode = mode; + + DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); + onStateChanged(); + } + @Override public void onBackPressed() { final int size = mState.stack.size(); @@ -481,6 +526,8 @@ public class DocumentsActivity extends Activity { title.setText(doc.displayName); } + // No padding when shown in actionbar + convertView.setPadding(0, 0, 0, 0); return convertView; } @@ -488,17 +535,20 @@ public class DocumentsActivity extends Activity { public View getDropDownView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()) - .inflate(android.R.layout.simple_dropdown_item_1line, parent, false); + .inflate(R.layout.item_title, parent, false); } - final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); + final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir); + final TextView title = (TextView) convertView.findViewById(android.R.id.title); final DocumentInfo doc = getItem(position); if (position == 0) { final RootInfo root = getCurrentRoot(); - text1.setText(root.title); + title.setText(root.title); + subdir.setVisibility(View.GONE); } else { - text1.setText(doc.displayName); + title.setText(doc.displayName); + subdir.setVisibility(View.VISIBLE); } return convertView; @@ -522,8 +572,8 @@ public class DocumentsActivity extends Activity { }; public RootInfo getCurrentRoot() { - if (mState.stack.size() > 0) { - return mState.stack.getRoot(mRoots); + if (mState.stack.root != null) { + return mState.stack.root; } else { return mRoots.getRecentsRoot(); } @@ -539,6 +589,7 @@ public class DocumentsActivity extends Activity { private void onCurrentDirectoryChanged() { final FragmentManager fm = getFragmentManager(); + final RootInfo root = getCurrentRoot(); final DocumentInfo cwd = getCurrentDirectory(); if (cwd == null) { @@ -551,10 +602,10 @@ public class DocumentsActivity extends Activity { } else { if (mState.currentSearch != null) { // Ongoing search - DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch); + DirectoryFragment.showSearch(fm, root, cwd, mState.currentSearch); } else { // Normal boring directory - DirectoryFragment.showNormal(fm, cwd.uri); + DirectoryFragment.showNormal(fm, root, cwd); } } @@ -566,16 +617,16 @@ public class DocumentsActivity extends Activity { } } + final RootsFragment roots = RootsFragment.get(fm); + if (roots != null) { + roots.onCurrentRootChanged(); + } + updateActionBar(); invalidateOptionsMenu(); dumpStack(); } - private void updateDisplayState() { - // TODO: handle multiple directory stacks on tablets - DirectoryFragment.get(getFragmentManager()).updateDisplayState(); - } - public void onStackPicked(DocumentStack stack) { mState.stack = stack; onCurrentDirectoryChanged(); @@ -583,6 +634,7 @@ public class DocumentsActivity extends Activity { public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root + mState.stack.root = root; mState.stack.clear(); if (!mRoots.isRecentsRoot(root)) { @@ -622,7 +674,7 @@ public class DocumentsActivity extends Activity { onCurrentDirectoryChanged(); } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Explicit file picked, return - onFinished(doc.uri); + onFinished(doc.derivedUri); } else if (mState.action == ACTION_CREATE) { // Replace selected file SaveFragment.get(fm).setReplaceTarget(doc); @@ -630,7 +682,7 @@ public class DocumentsActivity extends Activity { // First try managing the document; we expect manager to filter // based on authority, so we don't grant. final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT); - manage.setData(doc.uri); + manage.setData(doc.derivedUri); try { startActivity(manage); @@ -638,7 +690,7 @@ public class DocumentsActivity extends Activity { // Fall back to viewing final Intent view = new Intent(Intent.ACTION_VIEW); view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - view.setData(doc.uri); + view.setData(doc.derivedUri); try { startActivity(view); @@ -654,22 +706,21 @@ public class DocumentsActivity extends Activity { final int size = docs.size(); final Uri[] uris = new Uri[size]; for (int i = 0; i < size; i++) { - uris[i] = docs.get(i).uri; + uris[i] = docs.get(i).derivedUri; } onFinished(uris); } } public void onSaveRequested(DocumentInfo replaceTarget) { - onFinished(replaceTarget.uri); + onFinished(replaceTarget.derivedUri); } public void onSaveRequested(String mimeType, String displayName) { final DocumentInfo cwd = getCurrentDirectory(); - final String authority = cwd.uri.getAuthority(); final Uri childUri = DocumentsContract.createDocument( - getContentResolver(), cwd.uri, mimeType, displayName); + getContentResolver(), cwd.derivedUri, mimeType, displayName); if (childUri != null) { onFinished(childUri); } else { @@ -687,22 +738,14 @@ public class DocumentsActivity extends Activity { if (mState.action == ACTION_CREATE) { // Remember stack for last create values.clear(); - values.put(RecentsProvider.COL_PATH, rawStack); - resolver.insert(RecentsProvider.buildRecentCreate(), values); - - } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { - // Remember opened items - for (Uri uri : uris) { - values.clear(); - values.put(RecentsProvider.COL_URI, uri.toString()); - resolver.insert(RecentsProvider.buildRecentOpen(), values); - } + values.put(RecentColumns.STACK, rawStack); + resolver.insert(RecentsProvider.buildRecent(), values); } // Remember location for next app launch final String packageName = getCallingPackage(); values.clear(); - values.put(RecentsProvider.COL_PATH, rawStack); + values.put(ResumeColumns.STACK, rawStack); resolver.insert(RecentsProvider.buildResume(packageName), values); final Intent intent = new Intent(); @@ -749,12 +792,14 @@ public class DocumentsActivity extends Activity { public static final int ACTION_GET_CONTENT = 3; public static final int ACTION_MANAGE = 4; - public static final int MODE_LIST = 0; - public static final int MODE_GRID = 1; + public static final int MODE_UNKNOWN = 0; + public static final int MODE_LIST = 1; + public static final int MODE_GRID = 2; - public static final int SORT_ORDER_DISPLAY_NAME = 0; - public static final int SORT_ORDER_LAST_MODIFIED = 1; - public static final int SORT_ORDER_SIZE = 2; + public static final int SORT_ORDER_UNKNOWN = 0; + public static final int SORT_ORDER_DISPLAY_NAME = 1; + public static final int SORT_ORDER_LAST_MODIFIED = 2; + public static final int SORT_ORDER_SIZE = 3; @Override public int describeContents() { @@ -800,9 +845,10 @@ public class DocumentsActivity extends Activity { } private void dumpStack() { - Log.d(TAG, "Current stack:"); + Log.d(TAG, "Current stack: "); + Log.d(TAG, " * " + mState.stack.root); for (DocumentInfo doc : mState.stack) { - Log.d(TAG, "--> " + doc); + Log.d(TAG, " +-- " + doc); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java new file mode 100644 index 0000000..60f0103 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 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 static com.android.documentsui.DocumentsActivity.TAG; + +import android.database.AbstractCursor; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.DocumentsContract.Document; +import android.util.Log; + +/** + * Cursor wrapper that filters MIME types not matching given list. + */ +public class FilteringCursorWrapper extends AbstractCursor { + private final Cursor mCursor; + + private final int[] mPosition; + private int mCount; + + public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) { + mCursor = cursor; + + final int count = cursor.getCount(); + mPosition = new int[count]; + + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + final String mimeType = cursor.getString( + cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); + if (MimePredicate.mimeMatches(acceptMimes, mimeType)) { + mPosition[mCount++] = cursor.getPosition(); + } + } + + Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount); + } + + @Override + public Bundle getExtras() { + return mCursor.getExtras(); + } + + @Override + public void close() { + super.close(); + mCursor.close(); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + return mCursor.moveToPosition(mPosition[newPosition]); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public int getCount() { + return mCount; + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public int getType(int column) { + return mCursor.getType(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index 85d0988..b55ce82 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -49,6 +49,18 @@ public class MimePredicate implements Predicate<DocumentInfo> { return false; } + public static boolean mimeMatches(String filter, String[] tests) { + if (tests == null) { + return true; + } + for (String test : tests) { + if (mimeMatches(filter, test)) { + return true; + } + } + return false; + } + public static boolean mimeMatches(String[] filters, String test) { if (filters == null) { return true; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index 756a297..57442a0 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -17,6 +17,9 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.DocumentsActivity.State.MODE_GRID; +import static com.android.documentsui.DocumentsActivity.State.MODE_LIST; +import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; import android.content.AsyncTaskLoader; import android.content.ContentProviderClient; @@ -79,6 +82,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { } private final List<RootInfo> mRoots; + private final String[] mAcceptMimes; private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap(); @@ -135,9 +139,10 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { } } - public RecentLoader(Context context, List<RootInfo> roots) { + public RecentLoader(Context context, List<RootInfo> roots, String[] acceptMimes) { super(context); mRoots = roots; + mAcceptMimes = acceptMimes; } @Override @@ -171,7 +176,15 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { for (RecentTask task : mTasks.values()) { if (task.isDone()) { try { - cursors.add(task.get()); + final Cursor cursor = task.get(); + final FilteringCursorWrapper filtered = new FilteringCursorWrapper( + cursor, mAcceptMimes) { + @Override + public void close() { + // Ignored, since we manage cursor lifecycle internally + } + }; + cursors.add(filtered); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { @@ -181,15 +194,14 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { } final DirectoryResult result = new DirectoryResult(); + + final boolean acceptImages = MimePredicate.mimeMatches("image/*", mAcceptMimes); + result.mode = acceptImages ? MODE_GRID : MODE_LIST; + result.sortOrder = SORT_ORDER_LAST_MODIFIED; + if (cursors.size() > 0) { final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()])); - final SortingCursorWrapper sorted = new SortingCursorWrapper( - merged, State.SORT_ORDER_LAST_MODIFIED) { - @Override - public void close() { - // Ignored, since we manage cursor lifecycle internally - } - }; + final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder); result.cursor = sorted; } return result; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index fd7293d..9391ca9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -41,8 +41,8 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import com.android.documentsui.RecentsProvider.RecentColumns; import com.android.documentsui.model.DocumentStack; -import com.android.documentsui.model.RootInfo; import com.google.android.collect.Lists; import libcore.io.IoUtils; @@ -128,7 +128,7 @@ public class RecentsCreateFragment extends Fragment { public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> { public RecentsCreateLoader(Context context) { - super(context, RecentsProvider.buildRecentCreate()); + super(context, RecentsProvider.buildRecent()); } @Override @@ -137,14 +137,14 @@ public class RecentsCreateFragment extends Fragment { final ContentResolver resolver = getContext().getContentResolver(); final Cursor cursor = resolver.query( - uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal); + uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal); try { while (cursor != null && cursor.moveToNext()) { - final byte[] raw = cursor.getBlob( - cursor.getColumnIndex(RecentsProvider.COL_PATH)); + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(RecentColumns.STACK)); try { final DocumentStack stack = new DocumentStack(); - stack.read(new DataInputStream(new ByteArrayInputStream(raw))); + stack.read(new DataInputStream(new ByteArrayInputStream(rawStack))); result.add(stack); } catch (IOException e) { Log.w(TAG, "Failed to resolve stack: " + e); @@ -181,11 +181,9 @@ public class RecentsCreateFragment extends Fragment { final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); final TextView title = (TextView) convertView.findViewById(android.R.id.title); - final View summaryList = convertView.findViewById(R.id.summary_list); final DocumentStack stack = getItem(position); - final RootInfo root = stack.getRoot(roots); - icon.setImageDrawable(root.loadIcon(context)); + icon.setImageDrawable(stack.root.loadIcon(context)); final StringBuilder builder = new StringBuilder(); for (int i = stack.size() - 1; i >= 0; i--) { @@ -197,8 +195,6 @@ public class RecentsCreateFragment extends Fragment { title.setText(builder.toString()); title.setEllipsize(TruncateAt.MIDDLE); - summaryList.setVisibility(View.GONE); - return convertView; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 0c87783..df7ed4a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -25,51 +25,64 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; import android.text.format.DateUtils; import android.util.Log; public class RecentsProvider extends ContentProvider { private static final String TAG = "RecentsProvider"; - // TODO: offer view of recents that handles backend root resolution before - // returning cursor, include extra columns + public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45; - public static final String AUTHORITY = "com.android.documentsui.recents"; + private static final String AUTHORITY = "com.android.documentsui.recents"; private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); - private static final int URI_RECENT_OPEN = 1; - private static final int URI_RECENT_CREATE = 2; + private static final int URI_RECENT = 1; + private static final int URI_STATE = 2; private static final int URI_RESUME = 3; static { - sMatcher.addURI(AUTHORITY, "recent_open", URI_RECENT_OPEN); - sMatcher.addURI(AUTHORITY, "recent_create", URI_RECENT_CREATE); + sMatcher.addURI(AUTHORITY, "recent", URI_RECENT); + // state/authority/rootId/docId + sMatcher.addURI(AUTHORITY, "state/*/*/*", URI_STATE); + // resume/packageName sMatcher.addURI(AUTHORITY, "resume/*", URI_RESUME); } - private static final String TABLE_RECENT_OPEN = "recent_open"; - private static final String TABLE_RECENT_CREATE = "recent_create"; - private static final String TABLE_RESUME = "resume"; - - /** - * String of URIs pointing at a storage backend, stored as a JSON array, - * starting with root. - */ - public static final String COL_PATH = "path"; - public static final String COL_URI = "uri"; - public static final String COL_PACKAGE_NAME = "package_name"; - public static final String COL_TIMESTAMP = "timestamp"; - - @Deprecated - public static Uri buildRecentOpen() { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(AUTHORITY).appendPath("recent_open").build(); + public static final String TABLE_RECENT = "recent"; + public static final String TABLE_STATE = "state"; + public static final String TABLE_RESUME = "resume"; + + public static class RecentColumns { + public static final String STACK = "stack"; + public static final String TIMESTAMP = "timestamp"; + } + + public static class StateColumns { + public static final String AUTHORITY = "authority"; + public static final String ROOT_ID = Root.COLUMN_ROOT_ID; + public static final String DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID; + public static final String MODE = "mode"; + public static final String SORT_ORDER = "sortOrder"; } - public static Uri buildRecentCreate() { + public static class ResumeColumns { + public static final String PACKAGE_NAME = "package_name"; + public static final String STACK = "stack"; + public static final String TIMESTAMP = "timestamp"; + } + + public static Uri buildRecent() { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(AUTHORITY).appendPath("recent_create").build(); + .authority(AUTHORITY).appendPath("recent").build(); + } + + public static Uri buildState(String authority, String rootId, String documentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) + .appendPath("state").appendPath(authority).appendPath(rootId).appendPath(documentId) + .build(); } public static Uri buildResume(String packageName) { @@ -83,35 +96,42 @@ public class RecentsProvider extends ContentProvider { private static final String DB_NAME = "recents.db"; private static final int VERSION_INIT = 1; + private static final int VERSION_AS_BLOB = 3; public DatabaseHelper(Context context) { - super(context, DB_NAME, null, VERSION_INIT); + super(context, DB_NAME, null, VERSION_AS_BLOB); } @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_RECENT_OPEN + " (" + - COL_URI + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + - COL_TIMESTAMP + " INTEGER" + + + db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" + + RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," + + RecentColumns.TIMESTAMP + " INTEGER" + ")"); - db.execSQL("CREATE TABLE " + TABLE_RECENT_CREATE + " (" + - COL_PATH + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + - COL_TIMESTAMP + " INTEGER" + + db.execSQL("CREATE TABLE " + TABLE_STATE + " (" + + StateColumns.AUTHORITY + " TEXT," + + StateColumns.ROOT_ID + " TEXT," + + StateColumns.DOCUMENT_ID + " TEXT," + + StateColumns.MODE + " INTEGER," + + StateColumns.SORT_ORDER + " INTEGER," + + "PRIMARY KEY (" + StateColumns.AUTHORITY + ", " + StateColumns.ROOT_ID + ", " + + StateColumns.DOCUMENT_ID + ")" + ")"); db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" + - COL_PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + - COL_PATH + " TEXT," + - COL_TIMESTAMP + " INTEGER" + + ResumeColumns.PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + + ResumeColumns.STACK + " BLOB," + + ResumeColumns.TIMESTAMP + " INTEGER" + ")"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database; wiping app data"); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_OPEN); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_CREATE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATE); db.execSQL("DROP TABLE IF EXISTS " + TABLE_RESUME); onCreate(db); } @@ -128,22 +148,23 @@ public class RecentsProvider extends ContentProvider { String sortOrder) { final SQLiteDatabase db = mHelper.getReadableDatabase(); switch (sMatcher.match(uri)) { - case URI_RECENT_OPEN: { - return db.query(TABLE_RECENT_OPEN, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); - } - case URI_RECENT_CREATE: { - return db.query(TABLE_RECENT_CREATE, projection, - buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null); - } - case URI_RESUME: { + case URI_RECENT: + return db.query(TABLE_RECENT, projection, + RecentColumns.TIMESTAMP + "<" + MAX_HISTORY_IN_MILLIS, null, null, null, + null); + case URI_STATE: + final String authority = uri.getPathSegments().get(1); + final String rootId = uri.getPathSegments().get(2); + final String documentId = uri.getPathSegments().get(3); + return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND " + + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?", + new String[] { authority, rootId, documentId }, null, null, null); + case URI_RESUME: final String packageName = uri.getPathSegments().get(1); - return db.query(TABLE_RESUME, projection, COL_PACKAGE_NAME + "=?", + return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?", new String[] { packageName }, null, null, null); - } - default: { + default: throw new UnsupportedOperationException("Unsupported Uri " + uri); - } } } @@ -156,28 +177,37 @@ public class RecentsProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { final SQLiteDatabase db = mHelper.getWritableDatabase(); switch (sMatcher.match(uri)) { - case URI_RECENT_OPEN: { - values.put(COL_TIMESTAMP, System.currentTimeMillis()); - db.insert(TABLE_RECENT_OPEN, null, values); - db.delete(TABLE_RECENT_OPEN, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null); + case URI_RECENT: + values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis()); + db.insert(TABLE_RECENT, null, values); + db.delete( + TABLE_RECENT, RecentColumns.TIMESTAMP + ">" + MAX_HISTORY_IN_MILLIS, null); return uri; - } - case URI_RECENT_CREATE: { - values.put(COL_TIMESTAMP, System.currentTimeMillis()); - db.insert(TABLE_RECENT_CREATE, null, values); - db.delete(TABLE_RECENT_CREATE, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null); + case URI_STATE: + final String authority = uri.getPathSegments().get(1); + final String rootId = uri.getPathSegments().get(2); + final String documentId = uri.getPathSegments().get(3); + + final ContentValues key = new ContentValues(); + key.put(StateColumns.AUTHORITY, authority); + key.put(StateColumns.ROOT_ID, rootId); + key.put(StateColumns.DOCUMENT_ID, documentId); + + // Ensure that row exists, then update with changed values + db.insertWithOnConflict(TABLE_STATE, null, key, SQLiteDatabase.CONFLICT_IGNORE); + db.update(TABLE_STATE, values, StateColumns.AUTHORITY + "=? AND " + + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?", + new String[] { authority, rootId, documentId }); + return uri; - } - case URI_RESUME: { + case URI_RESUME: final String packageName = uri.getPathSegments().get(1); - values.put(COL_PACKAGE_NAME, packageName); - values.put(COL_TIMESTAMP, System.currentTimeMillis()); + values.put(ResumeColumns.PACKAGE_NAME, packageName); + values.put(ResumeColumns.TIMESTAMP, System.currentTimeMillis()); db.insert(TABLE_RESUME, null, values); return uri; - } - default: { + default: throw new UnsupportedOperationException("Unsupported Uri " + uri); - } } } @@ -190,12 +220,4 @@ public class RecentsProvider extends ContentProvider { public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Unsupported Uri " + uri); } - - private static String buildWhereOlder(long deltaMillis) { - return COL_TIMESTAMP + "<" + (System.currentTimeMillis() - deltaMillis); - } - - private static String buildWhereYounger(long deltaMillis) { - return COL_TIMESTAMP + ">" + (System.currentTimeMillis() - deltaMillis); - } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index d4f1b39..8530a9f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -169,8 +169,9 @@ public class RootsCache { if (state.localOnly && !localOnly) continue; // Only include roots that serve requested content - final boolean overlap = MimePredicate.mimeMatches(root.mimeTypes, state.acceptMimes) - || MimePredicate.mimeMatches(state.acceptMimes, root.mimeTypes); + final boolean overlap = + MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) || + MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes); if (!overlap) { continue; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index ef3a31d..efb972d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -40,6 +40,7 @@ import com.android.documentsui.DocumentsActivity.State; import com.android.documentsui.SectionedListAdapter.SectionAdapter; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; +import com.android.internal.util.Objects; import java.util.Comparator; import java.util.List; @@ -78,6 +79,7 @@ public class RootsFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_roots, container, false); mList = (ListView) view.findViewById(android.R.id.list); mList.setOnItemClickListener(mItemListener); + mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); return view; } @@ -100,6 +102,21 @@ public class RootsFragment extends Fragment { mAdapter = new SectionedRootsAdapter(context, matchingRoots, includeApps); mList.setAdapter(mAdapter); + + onCurrentRootChanged(); + } + + public void onCurrentRootChanged() { + if (mAdapter == null) return; + + final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot(); + for (int i = 0; i < mAdapter.getCount(); i++) { + final Object item = mAdapter.getItem(i); + if (Objects.equal(item, root)) { + mList.setItemChecked(i, true); + return; + } + } } private OnItemClickListener mItemListener = new OnItemClickListener() { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index a1489a5..c69103e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -20,6 +20,8 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; @@ -32,15 +34,16 @@ import java.io.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.ProtocolException; -import java.util.Comparator; /** * Representation of a {@link Document}. */ -public class DocumentInfo implements Durable { +public class DocumentInfo implements Durable, Parcelable { private static final int VERSION_INIT = 1; + private static final int VERSION_SPLIT_URI = 2; - public Uri uri; + public String authority; + public String documentId; public String mimeType; public String displayName; public long lastModified; @@ -49,13 +52,17 @@ public class DocumentInfo implements Durable { public long size; public int icon; + /** Derived fields that aren't persisted */ + public Uri derivedUri; + public DocumentInfo() { reset(); } @Override public void reset() { - uri = null; + authority = null; + documentId = null; mimeType = null; displayName = null; lastModified = -1; @@ -63,6 +70,8 @@ public class DocumentInfo implements Durable { summary = null; size = -1; icon = 0; + + derivedUri = null; } @Override @@ -70,8 +79,10 @@ public class DocumentInfo implements Durable { final int version = in.readInt(); switch (version) { case VERSION_INIT: - final String rawUri = DurableUtils.readNullableString(in); - uri = rawUri != null ? Uri.parse(rawUri) : null; + throw new ProtocolException("Ignored upgrade"); + case VERSION_SPLIT_URI: + authority = DurableUtils.readNullableString(in); + documentId = DurableUtils.readNullableString(in); mimeType = DurableUtils.readNullableString(in); displayName = DurableUtils.readNullableString(in); lastModified = in.readLong(); @@ -79,6 +90,7 @@ public class DocumentInfo implements Durable { summary = DurableUtils.readNullableString(in); size = in.readLong(); icon = in.readInt(); + deriveFields(); break; default: throw new ProtocolException("Unknown version " + version); @@ -87,8 +99,9 @@ public class DocumentInfo implements Durable { @Override public void write(DataOutputStream out) throws IOException { - out.writeInt(VERSION_INIT); - DurableUtils.writeNullableString(out, uri.toString()); + out.writeInt(VERSION_SPLIT_URI); + DurableUtils.writeNullableString(out, authority); + DurableUtils.writeNullableString(out, documentId); DurableUtils.writeNullableString(out, mimeType); DurableUtils.writeNullableString(out, displayName); out.writeLong(lastModified); @@ -98,11 +111,41 @@ public class DocumentInfo implements Durable { out.writeInt(icon); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + DurableUtils.writeToParcel(dest, this); + } + + public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() { + @Override + public DocumentInfo createFromParcel(Parcel in) { + final DocumentInfo doc = new DocumentInfo(); + DurableUtils.readFromParcel(in, doc); + return doc; + } + + @Override + public DocumentInfo[] newArray(int size) { + return new DocumentInfo[size]; + } + }; + public static DocumentInfo fromDirectoryCursor(Cursor cursor) { - final DocumentInfo doc = new DocumentInfo(); final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); - final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); - doc.uri = DocumentsContract.buildDocumentUri(authority, docId); + return fromCursor(cursor, authority); + } + + public static DocumentInfo fromCursor(Cursor cursor, String authority) { + final DocumentInfo doc = new DocumentInfo(); + doc.authority = authority; + doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); + doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); @@ -110,6 +153,7 @@ public class DocumentInfo implements Durable { doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY); doc.size = getCursorLong(cursor, Document.COLUMN_SIZE); doc.icon = getCursorInt(cursor, Document.COLUMN_ICON); + doc.deriveFields(); return doc; } @@ -122,16 +166,7 @@ public class DocumentInfo implements Durable { if (!cursor.moveToFirst()) { throw new FileNotFoundException("Missing details for " + uri); } - final DocumentInfo doc = new DocumentInfo(); - doc.uri = uri; - doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); - doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); - doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); - doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS); - doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY); - doc.size = getCursorLong(cursor, Document.COLUMN_SIZE); - doc.icon = getCursorInt(cursor, Document.COLUMN_ICON); - return doc; + return fromCursor(cursor, uri.getAuthority()); } catch (Throwable t) { throw asFileNotFoundException(t); } finally { @@ -140,9 +175,13 @@ public class DocumentInfo implements Durable { } } + private void deriveFields() { + derivedUri = DocumentsContract.buildDocumentUri(authority, documentId); + } + @Override public String toString() { - return "Document{name=" + displayName + ", uri=" + uri + "}"; + return "Document{name=" + displayName + ", docId=" + documentId + "}"; } public boolean isCreateSupported() { @@ -189,42 +228,14 @@ public class DocumentInfo implements Durable { } } + /** + * Missing or null values are returned as 0. + */ public static int getCursorInt(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); return (index != -1) ? cursor.getInt(index) : 0; } - @Deprecated - public static class DisplayNameComparator implements Comparator<DocumentInfo> { - @Override - public int compare(DocumentInfo lhs, DocumentInfo rhs) { - final boolean leftDir = lhs.isDirectory(); - final boolean rightDir = rhs.isDirectory(); - - if (leftDir != rightDir) { - return leftDir ? -1 : 1; - } else { - return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName); - } - } - } - - @Deprecated - public static class LastModifiedComparator implements Comparator<DocumentInfo> { - @Override - public int compare(DocumentInfo lhs, DocumentInfo rhs) { - return Long.compare(rhs.lastModified, lhs.lastModified); - } - } - - @Deprecated - public static class SizeComparator implements Comparator<DocumentInfo> { - @Override - public int compare(DocumentInfo lhs, DocumentInfo rhs) { - return Long.compare(rhs.size, lhs.size); - } - } - public static FileNotFoundException asFileNotFoundException(Throwable t) throws FileNotFoundException { if (t instanceof FileNotFoundException) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index 33a1376..2541440 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -16,8 +16,6 @@ package com.android.documentsui.model; -import com.android.documentsui.RootsCache; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -30,14 +28,13 @@ import java.util.LinkedList; */ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { private static final int VERSION_INIT = 1; + private static final int VERSION_ADD_ROOT = 2; - public RootInfo getRoot(RootsCache roots) { - return roots.findRoot(getLast().uri); - } + public RootInfo root; - public String getTitle(RootsCache roots) { - if (size() == 1) { - return getRoot(roots).title; + public String getTitle() { + if (size() == 1 && root != null) { + return root.title; } else if (size() > 1) { return peek().displayName; } else { @@ -52,6 +49,7 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { @Override public void reset() { clear(); + root = null; } @Override @@ -59,6 +57,12 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { final int version = in.readInt(); switch (version) { case VERSION_INIT: + throw new ProtocolException("Ignored upgrade"); + case VERSION_ADD_ROOT: + if (in.readBoolean()) { + root = new RootInfo(); + root.read(in); + } final int size = in.readInt(); for (int i = 0; i < size; i++) { final DocumentInfo doc = new DocumentInfo(); @@ -73,7 +77,13 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable { @Override public void write(DataOutputStream out) throws IOException { - out.writeInt(VERSION_INIT); + out.writeInt(VERSION_ADD_ROOT); + if (root != null) { + out.writeBoolean(true); + root.write(out); + } else { + out.writeBoolean(false); + } final int size = size(); out.writeInt(size); for (int i = 0; i < size; i++) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index a6ddf70..e0e8acf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -23,28 +23,121 @@ import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.content.Context; import android.database.Cursor; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; import android.provider.DocumentsContract.Root; import com.android.documentsui.IconUtils; import com.android.documentsui.R; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ProtocolException; import java.util.Objects; /** * Representation of a {@link Root}. */ -public class RootInfo { +public class RootInfo implements Durable, Parcelable { + private static final int VERSION_INIT = 1; + public String authority; public String rootId; public int rootType; public int flags; public int icon; - public int localIcon; public String title; public String summary; public String documentId; public long availableBytes; - public String[] mimeTypes; + public String mimeTypes; + + /** Derived fields that aren't persisted */ + public String[] derivedMimeTypes; + public int derivedIcon; + + public RootInfo() { + reset(); + } + + @Override + public void reset() { + authority = null; + rootId = null; + rootType = 0; + flags = 0; + icon = 0; + title = null; + summary = null; + documentId = null; + availableBytes = -1; + mimeTypes = null; + + derivedMimeTypes = null; + derivedIcon = 0; + } + + @Override + public void read(DataInputStream in) throws IOException { + final int version = in.readInt(); + switch (version) { + case VERSION_INIT: + authority = DurableUtils.readNullableString(in); + rootId = DurableUtils.readNullableString(in); + rootType = in.readInt(); + flags = in.readInt(); + icon = in.readInt(); + title = DurableUtils.readNullableString(in); + summary = DurableUtils.readNullableString(in); + documentId = DurableUtils.readNullableString(in); + availableBytes = in.readLong(); + mimeTypes = DurableUtils.readNullableString(in); + deriveFields(); + break; + default: + throw new ProtocolException("Unknown version " + version); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(VERSION_INIT); + DurableUtils.writeNullableString(out, authority); + DurableUtils.writeNullableString(out, rootId); + out.writeInt(rootType); + out.writeInt(flags); + out.writeInt(icon); + DurableUtils.writeNullableString(out, title); + DurableUtils.writeNullableString(out, summary); + DurableUtils.writeNullableString(out, documentId); + out.writeLong(availableBytes); + DurableUtils.writeNullableString(out, mimeTypes); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + DurableUtils.writeToParcel(dest, this); + } + + public static final Creator<RootInfo> CREATOR = new Creator<RootInfo>() { + @Override + public RootInfo createFromParcel(Parcel in) { + final RootInfo root = new RootInfo(); + DurableUtils.readFromParcel(in, root); + return root; + } + + @Override + public RootInfo[] newArray(int size) { + return new RootInfo[size]; + } + }; public static RootInfo fromRootsCursor(String authority, Cursor cursor) { final RootInfo root = new RootInfo(); @@ -57,31 +150,38 @@ public class RootInfo { root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY); root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID); root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES); + root.mimeTypes = getCursorString(cursor, Root.COLUMN_MIME_TYPES); + root.deriveFields(); + return root; + } - final String raw = getCursorString(cursor, Root.COLUMN_MIME_TYPES); - root.mimeTypes = (raw != null) ? raw.split("\n") : null; + private void deriveFields() { + derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null; // TODO: remove these special case icons if ("com.android.externalstorage.documents".equals(authority)) { - root.localIcon = R.drawable.ic_root_sdcard; + derivedIcon = R.drawable.ic_root_sdcard; } if ("com.android.providers.downloads.documents".equals(authority)) { - root.localIcon = R.drawable.ic_root_download; + derivedIcon = R.drawable.ic_root_download; } if ("com.android.providers.media.documents".equals(authority)) { - if ("image".equals(root.rootId)) { - root.localIcon = R.drawable.ic_doc_image; - } else if ("audio".equals(root.rootId)) { - root.localIcon = R.drawable.ic_doc_audio; + if ("image".equals(rootId)) { + derivedIcon = R.drawable.ic_doc_image; + } else if ("audio".equals(rootId)) { + derivedIcon = R.drawable.ic_doc_audio; } } + } - return root; + @Override + public String toString() { + return "Root{title=" + title + ", rootId=" + rootId + "}"; } public Drawable loadIcon(Context context) { - if (localIcon != 0) { - return context.getResources().getDrawable(localIcon); + if (derivedIcon != 0) { + return context.getResources().getDrawable(derivedIcon); } else { return IconUtils.loadPackageIcon(context, authority, icon); } diff --git a/packages/SystemUI/res/drawable-nodpi/lightning.png b/packages/SystemUI/res/drawable-nodpi/lightning.png Binary files differdeleted file mode 100644 index 29de308..0000000 --- a/packages/SystemUI/res/drawable-nodpi/lightning.png +++ /dev/null diff --git a/packages/SystemUI/res/values/arrays.xml b/packages/SystemUI/res/values/arrays.xml index 0812e80..b2c8aee 100644 --- a/packages/SystemUI/res/values/arrays.xml +++ b/packages/SystemUI/res/values/arrays.xml @@ -51,5 +51,14 @@ <item>#FFFF3300</item> <item>#FFFFFFFF</item> </array> - + <array name="batterymeter_bolt_points"> + <item>88</item> <item>0</item> + <item>459</item><item>1</item> + <item>238</item><item>333</item> + <item>525</item><item>310</item> + <item>120</item><item>840</item> + <item>82</item> <item>818</item> + <item>246</item><item>373</item> + <item>0</item> <item>408</item> + </array> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index be5c326..2257617 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -23,12 +23,13 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.LightingColorFilter; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; -import android.graphics.drawable.Drawable; import android.os.BatteryManager; import android.os.Bundle; import android.provider.Settings; @@ -43,26 +44,27 @@ public class BatteryMeterView extends View implements DemoMode { public static final boolean SINGLE_DIGIT_PERCENT = false; public static final boolean SHOW_100_PERCENT = false; - private static final LightingColorFilter LIGHTNING_FILTER_OPAQUE = - new LightingColorFilter(0x00000000, 0x00000000); - private static final LightingColorFilter LIGHTNING_FILTER_TRANS = - new LightingColorFilter(0x00999999, 0x00000000); - public static final int FULL = 96; public static final int EMPTY = 4; int[] mColors; boolean mShowPercent = true; - Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint; + Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint; int mButtonHeight; private float mTextHeight, mWarningTextHeight; - Drawable mLightning; private int mHeight; private int mWidth; private String mWarningString; private final int mChargeColor; + private final float[] mBoltPoints; + private final Path mBoltPath = new Path(); + + private final RectF mFrame = new RectF(); + private final RectF mButtonFrame = new RectF(); + private final RectF mClipFrame = new RectF(); + private final Rect mBoltFrame = new Rect(); private class BatteryTracker extends BroadcastReceiver { // current battery status @@ -175,7 +177,8 @@ public class BatteryMeterView extends View implements DemoMode { mColors[2*i] = levels.getInt(i, 0); mColors[2*i+1] = colors.getColor(i, 0); } - + levels.recycle(); + colors.recycle(); mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt( context.getContentResolver(), "status_bar_show_battery_percent", 0); @@ -198,8 +201,28 @@ public class BatteryMeterView extends View implements DemoMode { mWarningTextPaint.setTypeface(font); mWarningTextPaint.setTextAlign(Paint.Align.CENTER); - mLightning = getResources().getDrawable(R.drawable.lightning); mChargeColor = getResources().getColor(R.color.batterymeter_charge_color); + + mBoltPaint = new Paint(); + mBoltPaint.setAntiAlias(true); + mBoltPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); // punch hole + setLayerType(LAYER_TYPE_HARDWARE, null); + mBoltPoints = loadBoltPoints(res); + } + + private static float[] loadBoltPoints(Resources res) { + final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points); + int maxX = 0, maxY = 0; + for (int i = 0; i < pts.length; i += 2) { + maxX = Math.max(maxX, pts[i]); + maxY = Math.max(maxY, pts[i + 1]); + } + final float[] ptsF = new float[pts.length]; + for (int i = 0; i < pts.length; i += 2) { + ptsF[i] = (float)pts[i] / maxX; + ptsF[i + 1] = (float)pts[i + 1] / maxY; + } + return ptsF; } @Override @@ -220,15 +243,6 @@ public class BatteryMeterView extends View implements DemoMode { return color; } - // TODO jspurlock - remove once we draw hollow bolt in code - public void setBarTransparent(boolean isTransparent) { - mLightning.setColorFilter(isTransparent ? LIGHTNING_FILTER_TRANS : LIGHTNING_FILTER_OPAQUE); - BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker; - if (tracker.plugged) { - postInvalidate(); - } - } - @Override public void draw(Canvas c) { BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker; @@ -243,22 +257,19 @@ public class BatteryMeterView extends View implements DemoMode { mButtonHeight = (int) (height * 0.12f); - final RectF frame = new RectF(0, 0, width, height); - frame.offset(pl, pt); - - // Log.v("BatteryGauge", String.format("canvas: %dx%d frame: %s", - // c.getWidth(), c.getHeight(), frame.toString())); + mFrame.set(0, 0, width, height); + mFrame.offset(pl, pt); - final RectF buttonframe = new RectF( - frame.left + width * 0.25f, - frame.top, - frame.right - width * 0.25f, - frame.top + mButtonHeight); + mButtonFrame.set( + mFrame.left + width * 0.25f, + mFrame.top, + mFrame.right - width * 0.25f, + mFrame.top + mButtonHeight); - frame.top += mButtonHeight; + mFrame.top += mButtonHeight; // first, draw the battery shape - c.drawRect(frame, mFramePaint); + c.drawRect(mFrame, mFramePaint); // fill 'er up final int pct = tracker.level; @@ -271,15 +282,14 @@ public class BatteryMeterView extends View implements DemoMode { drawFrac = 0f; } - c.drawRect(buttonframe, - drawFrac == 1f ? mBatteryPaint : mFramePaint); + c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint); - RectF clip = new RectF(frame); - clip.top += (frame.height() * (1f - drawFrac)); + mClipFrame.set(mFrame); + mClipFrame.top += (mFrame.height() * (1f - drawFrac)); c.save(Canvas.CLIP_SAVE_FLAG); - c.clipRect(clip); - c.drawRect(frame, mBatteryPaint); + c.clipRect(mClipFrame); + c.drawRect(mFrame, mBatteryPaint); c.restore(); if (level <= EMPTY) { @@ -287,11 +297,28 @@ public class BatteryMeterView extends View implements DemoMode { final float y = (mHeight + mWarningTextHeight) * 0.48f; c.drawText(mWarningString, x, y, mWarningTextPaint); } else if (tracker.plugged) { - final Rect r = new Rect( - (int)frame.left + width / 4, (int)frame.top + height / 5, - (int)frame.right - width / 4, (int)frame.bottom - height / 6); - mLightning.setBounds(r); - mLightning.draw(c); + // draw the bolt + final int bl = (int)(mFrame.left + width / 4f); + final int bt = (int)(mFrame.top + height / 6f); + final int br = (int)(mFrame.right - width / 5f); + final int bb = (int)(mFrame.bottom - height / 6f); + if (mBoltFrame.left != bl || mBoltFrame.top != bt + || mBoltFrame.right != br || mBoltFrame.bottom != bb) { + mBoltFrame.set(bl, bt, br, bb); + mBoltPath.reset(); + mBoltPath.moveTo( + mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), + mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); + for (int i = 2; i < mBoltPoints.length; i += 2) { + mBoltPath.lineTo( + mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(), + mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height()); + } + mBoltPath.lineTo( + mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), + mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); + } + c.drawPath(mBoltPath, mBoltPaint); } else if (mShowPercent && !(tracker.level == 100 && !SHOW_100_PERCENT)) { mTextPaint.setTextSize(height * (SINGLE_DIGIT_PERCENT ? 0.75f diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index f8b6ca6..b263a6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.content.Context; @@ -28,7 +29,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; -import com.android.systemui.BatteryMeterView; import com.android.systemui.EventLogTags; import com.android.systemui.R; @@ -53,9 +53,7 @@ public class PhoneStatusBarView extends PanelBar { private final int mTransparent; private final float mAlphaWhenOpaque; private final float mAlphaWhenTransparent = 1; - private View mLeftSide; - private View mRightSide; - private BatteryMeterView mBattery; + private View mLeftSide, mStatusIcons, mSignalCluster, mClock; public StatusBarTransitions(Context context) { super(context, PhoneStatusBarView.this); @@ -66,8 +64,9 @@ public class PhoneStatusBarView extends PanelBar { public void init() { mLeftSide = findViewById(R.id.notification_icon_area); - mRightSide = findViewById(R.id.system_icon_area); - mBattery = (BatteryMeterView) findViewById(R.id.battery); + mStatusIcons = findViewById(R.id.statusIcons); + mSignalCluster = findViewById(R.id.signal_battery_cluster); + mClock = findViewById(R.id.clock); applyMode(getMode(), false /*animate*/); } @@ -96,17 +95,22 @@ public class PhoneStatusBarView extends PanelBar { } private void applyMode(int mode, boolean animate) { - if (mLeftSide == null || mRightSide == null) return; - mBattery.setBarTransparent(isTransparent(mode)); + if (mLeftSide == null) return; // pre-init float newAlpha = getAlphaFor(mode); if (animate) { - ObjectAnimator lhs = animateTransitionTo(mLeftSide, newAlpha); - lhs.start(); - // TODO jspurlock - fix conflicting rhs animations on tablets - mRightSide.setAlpha(newAlpha); + AnimatorSet anims = new AnimatorSet(); + anims.playTogether( + animateTransitionTo(mLeftSide, newAlpha), + animateTransitionTo(mStatusIcons, newAlpha), + animateTransitionTo(mSignalCluster, newAlpha), + animateTransitionTo(mClock, newAlpha) + ); + anims.start(); } else { mLeftSide.setAlpha(newAlpha); - mRightSide.setAlpha(newAlpha); + mStatusIcons.setAlpha(newAlpha); + mSignalCluster.setAlpha(newAlpha); + mClock.setAlpha(newAlpha); } } } |