diff options
Diffstat (limited to 'packages')
22 files changed, 984 insertions, 428 deletions
diff --git a/packages/DocumentsUI/res/drawable/item_background.xml b/packages/DocumentsUI/res/drawable/item_background.xml index 4fb32fc..7447aa8 100644 --- a/packages/DocumentsUI/res/drawable/item_background.xml +++ b/packages/DocumentsUI/res/drawable/item_background.xml @@ -15,12 +15,20 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_activated="true" android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" /> - <item android:state_activated="true" android:drawable="@*android:drawable/list_activated_holo" /> - <item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> - <item android:state_focused="true" android:state_enabled="false" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> - <item android:state_focused="true" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> - <item android:state_focused="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> - <item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" /> - <item android:drawable="@android:color/transparent" /> + + <item android:state_enabled="false" android:state_selected="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> + <item android:state_enabled="false" android:state_focused="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> + <item android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" /> + + <item android:state_activated="true" android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" /> + <item android:state_activated="true" android:drawable="@*android:drawable/list_activated_holo" /> + + <item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" /> + <item android:state_selected="true" android:drawable="@*android:drawable/list_focused_holo" /> + + <item android:state_pressed="true" android:state_focused="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> + <item android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" /> + + <item android:drawable="@android:color/transparent" /> + </selector> 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 e594437..45f028d 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; @@ -29,6 +31,7 @@ import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager.LoaderCallbacks; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.Loader; @@ -63,6 +66,7 @@ import android.widget.TextView; import android.widget.Toast; 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 com.android.internal.util.Predicate; @@ -91,43 +95,47 @@ 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); + /** + * MIME types that should always show thumbnails in list mode. + */ + private static final String[] LIST_THUMBNAIL_MIMES = new String[] { "image/*", "video/*" }; - private int mLastSortOrder = -1; + private static AtomicInteger sLoaderId = new AtomicInteger(4000); 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 +175,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 +183,50 @@ 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, state.userSortOrder); 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, state.userSortOrder); 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.derivedMode = result.mode; + state.derivedSortOrder = result.sortOrder; + ((DocumentsActivity) context).onStateChanged(); + + updateDisplayState(); + + if (mLastSortOrder != state.derivedSortOrder) { + mLastSortOrder = state.derivedSortOrder; + mListView.smoothScrollToPosition(0); + mGridView.smoothScrollToPosition(0); + } } @Override @@ -211,6 +235,9 @@ public class DirectoryFragment extends Fragment { } }; + // Kick off loader at least once + getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); + updateDisplayState(); } @@ -220,22 +247,51 @@ public class DirectoryFragment extends Fragment { updateDisplayState(); } - public void updateDisplayState() { + public void onUserSortOrderChanged() { + // Sort order change always triggers reload; we'll trigger state change + // on the flip side. + getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); + } + + public void onUserModeChanged() { + final ContentResolver resolver = getActivity().getContentResolver(); final State state = getDisplayState(this); - if (mLastSortOrder != state.sortOrder) { - getLoaderManager().restartLoader(mLoaderId, null, mCallbacks); - mLastSortOrder = state.sortOrder; - } + final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); + final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); - mListView.smoothScrollToPosition(0); - mGridView.smoothScrollToPosition(0); + final Uri stateUri = RecentsProvider.buildState( + root.authority, root.rootId, doc.documentId); + final ContentValues values = new ContentValues(); + values.put(StateColumns.MODE, state.userMode); - mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE); - mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + resolver.insert(stateUri, values); + return null; + } + }.execute(); + + // Mode change is just visual change; no need to kick loader, and + // deliver change event immediately. + state.derivedMode = state.userMode; + ((DocumentsActivity) getActivity()).onStateChanged(); + + updateDisplayState(); + } + + private void updateDisplayState() { + final State state = getDisplayState(this); mFilter = new MimePredicate(state.acceptMimes); + if (mLastMode == state.derivedMode) return; + mLastMode = state.derivedMode; + + mListView.setVisibility(state.derivedMode == MODE_LIST ? View.VISIBLE : View.GONE); + mGridView.setVisibility(state.derivedMode == MODE_GRID ? View.VISIBLE : View.GONE); + final int choiceMode; if (state.allowMultiple) { choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL; @@ -244,7 +300,7 @@ public class DirectoryFragment extends Fragment { } final int thumbSize; - if (state.mode == MODE_GRID) { + if (state.derivedMode == MODE_GRID) { thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width); mListView.setAdapter(null); mListView.setChoiceMode(ListView.CHOICE_MODE_NONE); @@ -253,15 +309,15 @@ public class DirectoryFragment extends Fragment { mGridView.setNumColumns(GridView.AUTO_FIT); mGridView.setChoiceMode(choiceMode); mCurrentView = mGridView; - } else if (state.mode == MODE_LIST) { - thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); + } else if (state.derivedMode == MODE_LIST) { + 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.derivedMode); } mThumbSize = new Point(thumbSize, thumbSize); @@ -271,9 +327,11 @@ public class DirectoryFragment extends Fragment { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Cursor cursor = mAdapter.getItem(position); - final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); - if (mFilter.apply(doc)) { - ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + if (cursor != null) { + final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor); + if (mFilter.apply(doc)) { + ((DocumentsActivity) getActivity()).onDocumentPicked(doc); + } } } }; @@ -344,10 +402,20 @@ public class DirectoryFragment extends Fragment { public void onItemCheckedStateChanged( ActionMode mode, int position, long id, boolean checked) { if (checked) { - // Directories cannot be checked + // Directories and footer items cannot be checked + boolean valid = false; + final Cursor cursor = mAdapter.getItem(position); - final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); - if (Document.MIME_TYPE_DIR.equals(docMimeType)) { + if (cursor != null) { + final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + + // Only valid if non-directory matches filter + final State state = getDisplayState(DirectoryFragment.this); + valid = !Document.MIME_TYPE_DIR.equals(docMimeType) + && MimePredicate.mimeMatches(state.acceptMimes, docMimeType); + } + + if (!valid) { mCurrentView.setItemChecked(position, false); } } @@ -366,7 +434,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); @@ -377,7 +445,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)); @@ -403,7 +471,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; } @@ -418,11 +486,25 @@ public class DirectoryFragment extends Fragment { return ((DocumentsActivity) fragment.getActivity()).getDisplayState(); } - private interface Footer { - public View getView(View convertView, ViewGroup parent); + private static abstract class Footer { + private final int mItemViewType; + + public Footer(int itemViewType) { + mItemViewType = itemViewType; + } + + public abstract View getView(View convertView, ViewGroup parent); + + public int getItemViewType() { + return mItemViewType; + } } - private static class LoadingFooter implements Footer { + private static class LoadingFooter extends Footer { + public LoadingFooter() { + super(1); + } + @Override public View getView(View convertView, ViewGroup parent) { final Context context = parent.getContext(); @@ -434,11 +516,12 @@ public class DirectoryFragment extends Fragment { } } - private class MessageFooter implements Footer { + private class MessageFooter extends Footer { private final int mIcon; private final String mMessage; - public MessageFooter(int icon, String message) { + public MessageFooter(int itemViewType, int icon, String message) { + super(itemViewType); mIcon = icon; mMessage = message; } @@ -450,9 +533,9 @@ public class DirectoryFragment extends Fragment { if (convertView == null) { final LayoutInflater inflater = LayoutInflater.from(context); - if (state.mode == MODE_LIST) { + if (state.derivedMode == MODE_LIST) { convertView = inflater.inflate(R.layout.item_message_list, parent, false); - } else if (state.mode == MODE_GRID) { + } else if (state.derivedMode == MODE_GRID) { convertView = inflater.inflate(R.layout.item_message_grid, parent, false); } else { throw new IllegalStateException(); @@ -483,11 +566,11 @@ public class DirectoryFragment extends Fragment { if (extras != null) { final String info = extras.getString(DocumentsContract.EXTRA_INFO); if (info != null) { - mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, info)); + mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_alert, info)); } final String error = extras.getString(DocumentsContract.EXTRA_ERROR); if (error != null) { - mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, error)); + mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error)); } if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) { mFooters.add(new LoadingFooter()); @@ -509,7 +592,11 @@ public class DirectoryFragment extends Fragment { return getDocumentView(position, convertView, parent); } else { position -= mCursorCount; - return mFooters.get(position).getView(convertView, parent); + convertView = mFooters.get(position).getView(convertView, parent); + // Only the view itself is disabled; contents inside shouldn't + // be dimmed. + convertView.setEnabled(false); + return convertView; } } @@ -523,9 +610,9 @@ public class DirectoryFragment extends Fragment { if (convertView == null) { final LayoutInflater inflater = LayoutInflater.from(context); - if (state.mode == MODE_LIST) { + if (state.derivedMode == MODE_LIST) { convertView = inflater.inflate(R.layout.item_doc_list, parent, false); - } else if (state.mode == MODE_GRID) { + } else if (state.derivedMode == MODE_GRID) { convertView = inflater.inflate(R.layout.item_doc_grid, parent, false); } else { throw new IllegalStateException(); @@ -558,7 +645,11 @@ public class DirectoryFragment extends Fragment { oldTask.cancel(false); } - if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) { + final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; + final boolean allowThumbnail = (state.derivedMode == MODE_GRID) + || MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType); + + if (supportsThumbnail && allowThumbnail) { final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); final Bitmap cachedResult = thumbs.get(uri); if (cachedResult != null) { @@ -567,7 +658,7 @@ public class DirectoryFragment extends Fragment { final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); icon.setImageBitmap(null); icon.setTag(task); - task.execute(uri); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri); } } else if (docIcon != 0) { icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon)); @@ -619,6 +710,18 @@ public class DirectoryFragment extends Fragment { line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE); + final boolean enabled = Document.MIME_TYPE_DIR.equals(docMimeType) + || MimePredicate.mimeMatches(state.acceptMimes, docMimeType); + if (enabled) { + setEnabledRecursive(convertView, true); + icon.setAlpha(1f); + icon1.setAlpha(1f); + } else { + setEnabledRecursive(convertView, false); + icon.setAlpha(0.5f); + icon1.setAlpha(0.5f); + } + return convertView; } @@ -643,23 +746,19 @@ public class DirectoryFragment extends Fragment { } @Override + public int getViewTypeCount() { + return 4; + } + + @Override public int getItemViewType(int position) { if (position < mCursorCount) { return 0; } else { - return IGNORE_ITEM_VIEW_TYPE; + position -= mCursorCount; + return mFooters.get(position).getItemViewType(); } } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - return position < mCursorCount; - } } private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { @@ -749,4 +848,16 @@ public class DirectoryFragment extends Fragment { return commonType[0] + "/" + commonType[1]; } + + private void setEnabledRecursive(View v, boolean enabled) { + if (v.isEnabled() == enabled) return; + v.setEnabled(enabled); + + if (v instanceof ViewGroup) { + final ViewGroup vg = (ViewGroup) v; + for (int i = vg.getChildCount() - 1; i >= 0; i--) { + setEnabledRecursive(vg.getChildAt(i), enabled); + } + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 6ea57d7..1471836 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,21 @@ 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 final int mUserSortOrder; 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, int userSortOrder) { super(context); - mRootId = rootId; + mRoot = root; + mDoc = doc; mUri = uri; - mSortOrder = sortOrder; + mUserSortOrder = userSortOrder; } @Override @@ -70,20 +87,63 @@ 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; + + // 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); + } + } 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 (mUserSortOrder != State.SORT_ORDER_UNKNOWN) { + result.sortOrder = mUserSortOrder; + } 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=" + mUserSortOrder + " --> 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 +151,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 38b2ee8..79d2443 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -60,6 +60,9 @@ 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; @@ -168,10 +171,6 @@ public class DocumentsActivity extends Activity { mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this); if (mState.action == ACTION_MANAGE) { - mState.sortOrder = SORT_ORDER_LAST_MODIFIED; - } - - if (mState.action == ACTION_MANAGE) { final Uri uri = intent.getData(); final String rootId = DocumentsContract.getRootId(uri); final RootInfo root = mRoots.getRoot(uri.getAuthority(), rootId); @@ -191,7 +190,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) { @@ -204,7 +203,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 @@ -343,11 +342,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.derivedMode != MODE_GRID); + list.setVisible(mState.derivedMode != 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); @@ -392,28 +396,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)); @@ -423,8 +418,36 @@ 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) { + mState.userSortOrder = sortOrder; + DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged(); + } + + /** + * Set state mode based on explicit user action. + */ + private void setUserMode(int mode) { + mState.userMode = mode; + DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); + } + @Override public void onBackPressed() { + if (!mState.stackTouched) { + super.onBackPressed(); + return; + } + final int size = mState.stack.size(); if (size > 1) { mState.stack.pop(); @@ -520,6 +543,7 @@ public class DocumentsActivity extends Activity { } while (mState.stack.size() > itemPosition + 1) { + mState.stackTouched = true; mState.stack.pop(); } onCurrentDirectoryChanged(); @@ -528,8 +552,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(); } @@ -545,6 +569,7 @@ public class DocumentsActivity extends Activity { private void onCurrentDirectoryChanged() { final FragmentManager fm = getFragmentManager(); + final RootInfo root = getCurrentRoot(); final DocumentInfo cwd = getCurrentDirectory(); if (cwd == null) { @@ -557,10 +582,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); } } @@ -582,19 +607,17 @@ public class DocumentsActivity extends Activity { dumpStack(); } - private void updateDisplayState() { - // TODO: handle multiple directory stacks on tablets - DirectoryFragment.get(getFragmentManager()).updateDisplayState(); - } - public void onStackPicked(DocumentStack stack) { mState.stack = stack; + mState.stackTouched = true; onCurrentDirectoryChanged(); } public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root + mState.stack.root = root; mState.stack.clear(); + mState.stackTouched = true; if (!mRoots.isRecentsRoot(root)) { try { @@ -623,17 +646,12 @@ public class DocumentsActivity extends Activity { public void onDocumentPicked(DocumentInfo doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { - // TODO: query display mode user preference for this dir - if (doc.isGridPreferred()) { - mState.mode = MODE_GRID; - } else { - mState.mode = MODE_LIST; - } mState.stack.push(doc); + mState.stackTouched = true; 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); @@ -641,7 +659,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); @@ -649,7 +667,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); @@ -665,22 +683,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 { @@ -698,22 +715,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(); @@ -742,13 +751,23 @@ public class DocumentsActivity extends Activity { public static class State implements android.os.Parcelable { public int action; - public int mode = MODE_LIST; public String[] acceptMimes; - public int sortOrder = SORT_ORDER_DISPLAY_NAME; + + /** Explicit user choice */ + public int userMode = MODE_UNKNOWN; + /** Derived after loader */ + public int derivedMode = MODE_LIST; + + /** Explicit user choice */ + public int userSortOrder = SORT_ORDER_UNKNOWN; + /** Derived after loader */ + public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME; + public boolean allowMultiple = false; public boolean showSize = false; public boolean localOnly = false; public boolean showAdvanced = false; + public boolean stackTouched = false; /** Current user navigation stack; empty implies recents. */ public DocumentStack stack = new DocumentStack(); @@ -760,12 +779,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() { @@ -775,13 +796,14 @@ public class DocumentsActivity extends Activity { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(action); - out.writeInt(mode); + out.writeInt(userMode); out.writeStringArray(acceptMimes); - out.writeInt(sortOrder); + out.writeInt(userSortOrder); out.writeInt(allowMultiple ? 1 : 0); out.writeInt(showSize ? 1 : 0); out.writeInt(localOnly ? 1 : 0); out.writeInt(showAdvanced ? 1 : 0); + out.writeInt(stackTouched ? 1 : 0); DurableUtils.writeToParcel(out, stack); out.writeString(currentSearch); } @@ -791,13 +813,14 @@ public class DocumentsActivity extends Activity { public State createFromParcel(Parcel in) { final State state = new State(); state.action = in.readInt(); - state.mode = in.readInt(); + state.userMode = in.readInt(); state.acceptMimes = in.readStringArray(); - state.sortOrder = in.readInt(); + state.userSortOrder = in.readInt(); state.allowMultiple = in.readInt() != 0; state.showSize = in.readInt() != 0; state.localOnly = in.readInt() != 0; state.showAdvanced = in.readInt() != 0; + state.stackTouched = in.readInt() != 0; DurableUtils.readFromParcel(in, state.stack); state.currentSearch = in.readString(); return state; @@ -811,9 +834,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 461c415..3642478 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -26,10 +26,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Loader; import android.database.Cursor; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.text.TextUtils.TruncateAt; +import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -41,8 +45,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 +132,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 +141,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,21 +185,29 @@ 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 line2 = convertView.findViewById(R.id.line2); 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--) { + final Drawable crumb = context.getResources() + .getDrawable(R.drawable.ic_breadcrumb_arrow); + crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight()); + + final SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(stack.root.title); + appendDrawable(builder, crumb); + for (int i = stack.size() - 2; i >= 0; i--) { builder.append(stack.get(i).displayName); if (i > 0) { - builder.append(" \u232a "); + appendDrawable(builder, crumb); } } - title.setText(builder.toString()); + title.setText(builder); title.setEllipsize(TruncateAt.MIDDLE); + line2.setVisibility(View.GONE); + return convertView; } @@ -214,4 +226,10 @@ public class RecentsCreateFragment extends Fragment { return getItem(position).hashCode(); } } + + private static void appendDrawable(SpannableStringBuilder b, Drawable d) { + final int length = b.length(); + b.append("\u232a"); + b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 0c87783..1fe5d54 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: + final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS; + return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff, + 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); + final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS; + db.delete(TABLE_RECENT, RecentColumns.TIMESTAMP + "<" + cutoff, 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..adf4701 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -90,7 +90,6 @@ public class RootsCache { if (info.metaData != null && info.metaData.containsKey( DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { - // TODO: remove deprecated customRoots flag // TODO: populate roots on background thread, and cache results final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority); final ContentProviderClient client = resolver @@ -169,8 +168,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/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/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java index f53e60d..cdb6b33 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java @@ -30,7 +30,7 @@ public class RootsCacheTest extends AndroidTestCase { private static RootInfo buildForMimeTypes(String... mimeTypes) { final RootInfo root = new RootInfo(); - root.mimeTypes = mimeTypes; + root.derivedMimeTypes = mimeTypes; return root; } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 226d635..2326ec2 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -181,12 +181,12 @@ public class ExternalStorageProvider extends DocumentsProvider { } final RowBuilder row = result.newRow(); - row.offer(Document.COLUMN_DOCUMENT_ID, docId); - row.offer(Document.COLUMN_DISPLAY_NAME, displayName); - row.offer(Document.COLUMN_SIZE, file.length()); - row.offer(Document.COLUMN_MIME_TYPE, mimeType); - row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified()); - row.offer(Document.COLUMN_FLAGS, flags); + row.add(Document.COLUMN_DOCUMENT_ID, docId); + row.add(Document.COLUMN_DISPLAY_NAME, displayName); + row.add(Document.COLUMN_SIZE, file.length()); + row.add(Document.COLUMN_MIME_TYPE, mimeType); + row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + row.add(Document.COLUMN_FLAGS, flags); } @Override @@ -197,13 +197,13 @@ public class ExternalStorageProvider extends DocumentsProvider { final File path = mIdToPath.get(rootId); final RowBuilder row = result.newRow(); - row.offer(Root.COLUMN_ROOT_ID, root.rootId); - row.offer(Root.COLUMN_ROOT_TYPE, root.rootType); - row.offer(Root.COLUMN_FLAGS, root.flags); - row.offer(Root.COLUMN_ICON, root.icon); - row.offer(Root.COLUMN_TITLE, root.title); - row.offer(Root.COLUMN_DOCUMENT_ID, root.docId); - row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); + row.add(Root.COLUMN_ROOT_ID, root.rootId); + row.add(Root.COLUMN_ROOT_TYPE, root.rootType); + row.add(Root.COLUMN_FLAGS, root.flags); + row.add(Root.COLUMN_ICON, root.icon); + row.add(Root.COLUMN_TITLE, root.title); + row.add(Root.COLUMN_DOCUMENT_ID, root.docId); + row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); } return result; } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java index 872974f..014c664 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java @@ -69,13 +69,13 @@ public class TestDocumentsProvider extends DocumentsProvider { final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); final RowBuilder row = result.newRow(); - row.offer(Root.COLUMN_ROOT_ID, MY_ROOT_ID); - row.offer(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); - row.offer(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); - row.offer(Root.COLUMN_TITLE, "_Test title which is really long"); - row.offer(Root.COLUMN_SUMMARY, "_Summary which is also super long text"); - row.offer(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID); - row.offer(Root.COLUMN_AVAILABLE_BYTES, 1024); + row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID); + row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); + row.add(Root.COLUMN_TITLE, "_Test title which is really long"); + row.add(Root.COLUMN_SUMMARY, "_Summary which is also super long text"); + row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID); + row.add(Root.COLUMN_AVAILABLE_BYTES, 1024); return result; } @@ -125,6 +125,9 @@ public class TestDocumentsProvider extends DocumentsProvider { includeFile(result, "_networkfile1"); includeFile(result, "_networkfile2"); includeFile(result, "_networkfile3"); + includeFile(result, "_networkfile4"); + includeFile(result, "_networkfile5"); + includeFile(result, "_networkfile6"); return true; } else { return false; @@ -162,6 +165,8 @@ public class TestDocumentsProvider extends DocumentsProvider { includeFile(result, MY_DOC_NULL); includeFile(result, "localfile1"); includeFile(result, "localfile2"); + includeFile(result, "localfile3"); + includeFile(result, "localfile4"); synchronized (this) { // Try picking up an existing network fetch @@ -229,16 +234,16 @@ public class TestDocumentsProvider extends DocumentsProvider { private static void includeFile(MatrixCursor result, String docId) { final RowBuilder row = result.newRow(); - row.offer(Document.COLUMN_DOCUMENT_ID, docId); - row.offer(Document.COLUMN_DISPLAY_NAME, docId); - row.offer(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); + row.add(Document.COLUMN_DOCUMENT_ID, docId); + row.add(Document.COLUMN_DISPLAY_NAME, docId); + row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); if (MY_DOC_ID.equals(docId)) { - row.offer(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); + row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); } else if (MY_DOC_NULL.equals(docId)) { // No MIME type } else { - row.offer(Document.COLUMN_MIME_TYPE, "application/octet-stream"); + row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream"); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index 78b842c..40a1af6 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -50,6 +50,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.speech.hotword.HotwordRecognitionListener; +import android.speech.hotword.HotwordRecognitionService; import android.speech.hotword.HotwordRecognizer; import android.telephony.TelephonyManager; import android.util.AttributeSet; @@ -1775,11 +1776,13 @@ public class KeyguardHostView extends KeyguardViewBase { public void onHotwordEvent(int eventType, Bundle eventBundle) { if (DEBUG) Log.d(TAG, "onHotwordEvent: " + eventType); - if (eventType == HotwordRecognizer.EVENT_TYPE_STATE_CHANGED) { - if (eventBundle != null && eventBundle.containsKey(HotwordRecognizer.PROMPT_TEXT)) { - new KeyguardMessageArea.Helper( - (View) getSecurityView(mCurrentSecuritySelection)) - .setMessage(eventBundle.getString(HotwordRecognizer.PROMPT_TEXT),true); + if (eventType == HotwordRecognitionService.EVENT_TYPE_PROMPT_CHANGED) { + if (eventBundle != null + && eventBundle.containsKey(HotwordRecognitionService.KEY_PROMPT_TEXT)) { + new KeyguardMessageArea + .Helper((View) getSecurityView(mCurrentSecuritySelection)) + .setMessage(eventBundle.getString( + HotwordRecognitionService.KEY_PROMPT_TEXT),true); } } } 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); } } } |