diff options
Diffstat (limited to 'packages/DocumentsUI')
15 files changed, 365 insertions, 36 deletions
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 6faf7f8..179bcd1 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -50,6 +50,14 @@ android:authorities="com.android.documentsui.recents" android:exported="false" /> + <receiver android:name=".PackageReceiver"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> + <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> + <data android:scheme="package" /> + </intent-filter> + </receiver> + <!-- TODO: remove when we have real clients --> <activity android:name=".TestActivity" android:enabled="false"> <intent-filter> diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml index 891f0a0..d601194 100644 --- a/packages/DocumentsUI/res/layout/fragment_save.xml +++ b/packages/DocumentsUI/res/layout/fragment_save.xml @@ -51,15 +51,31 @@ android:singleLine="true" android:selectAllOnFocus="true" /> - <Button - android:id="@android:id/button1" + <FrameLayout android:layout_width="wrap_content" - android:layout_height="match_parent" - android:background="?android:attr/selectableItemBackground" - android:text="@string/menu_save" - android:textAllCaps="true" - android:textAppearance="?android:attr/textAppearanceSmall" - android:padding="8dp" /> + android:layout_height="match_parent"> + + <Button + android:id="@android:id/button1" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="?android:attr/selectableItemBackground" + android:text="@string/menu_save" + android:textAllCaps="true" + android:textAppearance="?android:attr/textAppearanceSmall" + android:padding="8dp" /> + + <ProgressBar + android:id="@android:id/progress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="gone" + android:indeterminate="true" + android:padding="8dp" + style="?android:attr/progressBarStyle" /> + + </FrameLayout> </LinearLayout> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 90be197..ba8c35f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -95,6 +95,11 @@ public class CreateDirectoryFragment extends DialogFragment { } @Override + protected void onPreExecute() { + mActivity.setPending(true); + } + + @Override protected DocumentInfo doInBackground(Void... params) { final ContentResolver resolver = mActivity.getContentResolver(); ContentProviderClient client = null; @@ -120,6 +125,8 @@ public class CreateDirectoryFragment extends DialogFragment { } else { Toast.makeText(mActivity, R.string.create_error, Toast.LENGTH_SHORT).show(); } + + mActivity.setPending(false); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 1f3901c..b2b2bd8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -47,6 +47,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; @@ -76,6 +77,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; @@ -83,7 +85,6 @@ import com.google.android.collect.Lists; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; /** * Display the documents inside a single directory. @@ -126,9 +127,7 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_QUERY = "query"; private static final String EXTRA_IGNORE_STATE = "ignoreState"; - private static AtomicInteger sLoaderId = new AtomicInteger(4000); - - private final int mLoaderId = sLoaderId.incrementAndGet(); + private final int mLoaderId = 42; public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) { show(fm, TYPE_NORMAL, root, doc, null, anim); @@ -528,7 +527,7 @@ public class DirectoryFragment extends Fragment { if (iconThumb != null) { final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); if (oldTask != null) { - oldTask.reallyCancel(); + oldTask.preempt(); iconThumb.setTag(null); } } @@ -794,7 +793,7 @@ public class DirectoryFragment extends Fragment { final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); if (oldTask != null) { - oldTask.reallyCancel(); + oldTask.preempt(); iconThumb.setTag(null); } @@ -818,7 +817,7 @@ public class DirectoryFragment extends Fragment { final ThumbnailAsyncTask task = new ThumbnailAsyncTask( uri, iconMime, iconThumb, mThumbSize); iconThumb.setTag(task); - task.executeOnExecutor(ProviderExecutor.forAuthority(docAuthority)); + ProviderExecutor.forAuthority(docAuthority).execute(task); } } @@ -988,7 +987,8 @@ public class DirectoryFragment extends Fragment { } } - private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { + private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> + implements Preemptable { private final Uri mUri; private final ImageView mIconMime; private final ImageView mIconThumb; @@ -1004,7 +1004,8 @@ public class DirectoryFragment extends Fragment { mSignal = new CancellationSignal(); } - public void reallyCancel() { + @Override + public void preempt() { cancel(false); mSignal.cancel(); } @@ -1028,7 +1029,9 @@ public class DirectoryFragment extends Fragment { thumbs.put(mUri, result); } } catch (Exception e) { - Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e); + if (!(e instanceof OperationCanceledException)) { + Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e); + } } finally { ContentProviderClient.releaseQuietly(client); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index d675e8d..4212e96 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -255,7 +255,9 @@ public class DocumentsActivity extends Activity { } mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); - mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this); + mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false); + mState.showAdvanced = mState.forceAdvanced + | SettingsActivity.getDisplayAdvancedDevices(this); } private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> { @@ -661,6 +663,13 @@ public class DocumentsActivity extends Activity { DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); } + public void setPending(boolean pending) { + final SaveFragment save = SaveFragment.get(getFragmentManager()); + if (save != null) { + save.setPending(pending); + } + } + @Override public void onBackPressed() { if (!mState.stackTouched) { @@ -1051,6 +1060,11 @@ public class DocumentsActivity extends Activity { } @Override + protected void onPreExecute() { + setPending(true); + } + + @Override protected Uri doInBackground(Void... params) { final ContentResolver resolver = getContentResolver(); final DocumentInfo cwd = getCurrentDirectory(); @@ -1083,6 +1097,8 @@ public class DocumentsActivity extends Activity { Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT) .show(); } + + setPending(false); } } @@ -1122,6 +1138,7 @@ public class DocumentsActivity extends Activity { public boolean allowMultiple = false; public boolean showSize = false; public boolean localOnly = false; + public boolean forceAdvanced = false; public boolean showAdvanced = false; public boolean stackTouched = false; public boolean restored = false; @@ -1162,6 +1179,7 @@ public class DocumentsActivity extends Activity { out.writeInt(allowMultiple ? 1 : 0); out.writeInt(showSize ? 1 : 0); out.writeInt(localOnly ? 1 : 0); + out.writeInt(forceAdvanced ? 1 : 0); out.writeInt(showAdvanced ? 1 : 0); out.writeInt(stackTouched ? 1 : 0); out.writeInt(restored ? 1 : 0); @@ -1181,6 +1199,7 @@ public class DocumentsActivity extends Activity { state.allowMultiple = in.readInt() != 0; state.showSize = in.readInt() != 0; state.localOnly = in.readInt() != 0; + state.forceAdvanced = in.readInt() != 0; state.showAdvanced = in.readInt() != 0; state.stackTouched = in.readInt() != 0; state.restored = in.readInt() != 0; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java index 6b46e3a..547e343 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java @@ -75,6 +75,7 @@ public class DocumentsApplication extends Application { packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); packageFilter.addDataScheme("package"); registerReceiver(mCacheReceiver, packageFilter); diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 52d816f..55d73f2 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -17,6 +17,8 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.database.AbstractCursor; import android.database.Cursor; @@ -50,10 +52,8 @@ public class FilteringCursorWrapper extends AbstractCursor { cursor.moveToPosition(-1); while (cursor.moveToNext()) { - final String mimeType = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); - final long lastModified = cursor.getLong( - cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { continue; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java new file mode 100644 index 0000000..aef63af --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java @@ -0,0 +1,46 @@ +/* + * 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 android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +/** + * Clean up {@link RecentsProvider} when packages are removed. + */ +public class PackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final ContentResolver resolver = context.getContentResolver(); + + final String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE, null, null); + + } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { + final Uri data = intent.getData(); + if (data != null) { + final String packageName = data.getSchemeSpecificPart(); + resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE_PACKAGE, + packageName, null); + } + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java b/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java index 2105cb4..f94aebd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java +++ b/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java @@ -16,10 +16,15 @@ package com.android.documentsui; +import android.os.AsyncTask; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -29,7 +34,7 @@ public class ProviderExecutor extends Thread implements Executor { @GuardedBy("sExecutors") private static HashMap<String, ProviderExecutor> sExecutors = Maps.newHashMap(); - public static Executor forAuthority(String authority) { + public static ProviderExecutor forAuthority(String authority) { synchronized (sExecutors) { ProviderExecutor executor = sExecutors.get(authority); if (executor == null) { @@ -42,10 +47,54 @@ public class ProviderExecutor extends Thread implements Executor { } } + public interface Preemptable { + void preempt(); + } + private final LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<Runnable>(); + private final ArrayList<WeakReference<Preemptable>> mPreemptable = Lists.newArrayList(); + + private void preempt() { + synchronized (mPreemptable) { + int count = 0; + for (WeakReference<Preemptable> ref : mPreemptable) { + final Preemptable p = ref.get(); + if (p != null) { + count++; + p.preempt(); + } + } + mPreemptable.clear(); + } + } + + /** + * Execute the given task. If given task is not {@link Preemptable}, it will + * preempt all outstanding preemptable tasks. + */ + public <P> void execute(AsyncTask<P, ?, ?> task, P... params) { + if (task instanceof Preemptable) { + synchronized (mPreemptable) { + mPreemptable.add(new WeakReference<Preemptable>((Preemptable) task)); + } + task.executeOnExecutor(mNonPreemptingExecutor, params); + } else { + task.executeOnExecutor(this, params); + } + } + + private Executor mNonPreemptingExecutor = new Executor() { + @Override + public void execute(Runnable command) { + Preconditions.checkNotNull(command); + mQueue.add(command); + } + }; + @Override public void execute(Runnable command) { + preempt(); Preconditions.checkNotNull(command); mQueue.add(command); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 4313fa7..f6e4349 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -16,24 +16,40 @@ package com.android.documentsui; +import static com.android.documentsui.model.DocumentInfo.getCursorString; + import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.content.UriMatcher; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; +import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.text.format.DateUtils; import android.util.Log; +import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.DurableUtils; +import com.android.internal.util.Predicate; +import com.google.android.collect.Sets; + +import libcore.io.IoUtils; + +import java.io.IOException; +import java.util.Set; + public class RecentsProvider extends ContentProvider { private static final String TAG = "RecentsProvider"; - public static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS; + private static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS; private static final String AUTHORITY = "com.android.documentsui.recents"; @@ -43,6 +59,9 @@ public class RecentsProvider extends ContentProvider { private static final int URI_STATE = 2; private static final int URI_RESUME = 3; + public static final String METHOD_PURGE = "purge"; + public static final String METHOD_PURGE_PACKAGE = "purgePackage"; + static { sMatcher.addURI(AUTHORITY, "recent", URI_RECENT); // state/authority/rootId/docId @@ -231,4 +250,116 @@ public class RecentsProvider extends ContentProvider { public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Unsupported Uri " + uri); } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (METHOD_PURGE.equals(method)) { + // Purge references to unknown authorities + final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); + final Set<String> knownAuth = Sets.newHashSet(); + for (ResolveInfo info : getContext() + .getPackageManager().queryIntentContentProviders(intent, 0)) { + knownAuth.add(info.providerInfo.authority); + } + + purgeByAuthority(new Predicate<String>() { + @Override + public boolean apply(String authority) { + // Purge unknown authorities + return !knownAuth.contains(authority); + } + }); + + return null; + + } else if (METHOD_PURGE_PACKAGE.equals(method)) { + // Purge references to authorities in given package + final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); + intent.setPackage(arg); + final Set<String> packageAuth = Sets.newHashSet(); + for (ResolveInfo info : getContext() + .getPackageManager().queryIntentContentProviders(intent, 0)) { + packageAuth.add(info.providerInfo.authority); + } + + if (!packageAuth.isEmpty()) { + purgeByAuthority(new Predicate<String>() { + @Override + public boolean apply(String authority) { + // Purge authority matches + return packageAuth.contains(authority); + } + }); + } + + return null; + + } else { + return super.call(method, arg, extras); + } + } + + /** + * Purge all internal data whose authority matches the given + * {@link Predicate}. + */ + private void purgeByAuthority(Predicate<String> predicate) { + final SQLiteDatabase db = mHelper.getWritableDatabase(); + final DocumentStack stack = new DocumentStack(); + + Cursor cursor = db.query(TABLE_RECENT, null, null, null, null, null, null); + try { + while (cursor.moveToNext()) { + try { + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(RecentColumns.STACK)); + DurableUtils.readFromArray(rawStack, stack); + + if (stack.root != null && predicate.apply(stack.root.authority)) { + final String key = getCursorString(cursor, RecentColumns.KEY); + db.delete(TABLE_RECENT, RecentColumns.KEY + "=?", new String[] { key }); + } + } catch (IOException ignored) { + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + + cursor = db.query(TABLE_STATE, new String[] { + StateColumns.AUTHORITY }, null, null, StateColumns.AUTHORITY, null, null); + try { + while (cursor.moveToNext()) { + final String authority = getCursorString(cursor, StateColumns.AUTHORITY); + if (predicate.apply(authority)) { + db.delete(TABLE_STATE, StateColumns.AUTHORITY + "=?", new String[] { + authority }); + Log.d(TAG, "Purged state for " + authority); + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + + cursor = db.query(TABLE_RESUME, null, null, null, null, null, null); + try { + while (cursor.moveToNext()) { + try { + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(ResumeColumns.STACK)); + DurableUtils.readFromArray(rawStack, stack); + + if (stack.root != null && predicate.apply(stack.root.authority)) { + final String packageName = getCursorString( + cursor, ResumeColumns.PACKAGE_NAME); + db.delete(TABLE_RESUME, ResumeColumns.PACKAGE_NAME + "=?", + new String[] { packageName }); + } + } catch (IOException ignored) { + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index b98e1ee..f6b43c7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -60,8 +60,8 @@ import java.util.concurrent.TimeUnit; public class RootsCache { private static final boolean LOGD = true; - // TODO: cache roots in local provider to avoid spinning up backends - // TODO: root updates should trigger UI refresh + public static final Uri sNotificationUri = Uri.parse( + "content://com.android.documentsui.roots/"); private final Context mContext; private final ContentObserver mObserver; @@ -201,6 +201,7 @@ public class RootsCache { mStoppedAuthorities = mTaskStoppedAuthorities; } mFirstLoad.countDown(); + resolver.notifyChange(sNotificationUri, null, false); return null; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index fdbc3ab..931dac9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; + import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -25,7 +27,9 @@ import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; @@ -33,6 +37,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; @@ -131,7 +136,15 @@ public class RootsFragment extends Fragment { final Context context = getActivity(); final State state = ((DocumentsActivity) context).getDisplayState(); - state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context); + state.showAdvanced = state.forceAdvanced + | SettingsActivity.getDisplayAdvancedDevices(context); + + if (state.action == ACTION_GET_CONTENT) { + mList.setOnItemLongClickListener(mItemLongClickListener); + } else { + mList.setOnItemLongClickListener(null); + mList.setLongClickable(false); + } getLoaderManager().restartLoader(2, null, mCallbacks); } @@ -152,6 +165,13 @@ public class RootsFragment extends Fragment { } } + private void showAppDetails(ResolveInfo ri) { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + startActivity(intent); + } + private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { @@ -167,6 +187,19 @@ public class RootsFragment extends Fragment { } }; + private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + final Item item = mAdapter.getItem(position); + if (item instanceof AppItem) { + showAppDetails(((AppItem) item).info); + return true; + } else { + return false; + } + } + }; + private static abstract class Item { private final int mLayoutId; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java index 7108971..8d37cdf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java @@ -25,6 +25,8 @@ import com.android.documentsui.model.RootInfo; import java.util.Collection; public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { + private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); + private final RootsCache mRoots; private final State mState; @@ -34,6 +36,9 @@ public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { super(context); mRoots = roots; mState = state; + + getContext().getContentResolver() + .registerContentObserver(RootsCache.sNotificationUri, false, mObserver); } @Override @@ -77,5 +82,7 @@ public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { onStopLoading(); mResult = null; + + getContext().getContentResolver().unregisterContentObserver(mObserver); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 23e047c..9d70c51 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -30,6 +30,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.ProgressBar; import com.android.documentsui.model.DocumentInfo; @@ -42,6 +43,7 @@ public class SaveFragment extends Fragment { private DocumentInfo mReplaceTarget; private EditText mDisplayName; private Button mSave; + private ProgressBar mProgress; private boolean mIgnoreNextEdit; private static final String EXTRA_MIME_TYPE = "mime_type"; @@ -83,6 +85,8 @@ public class SaveFragment extends Fragment { mSave.setOnClickListener(mSaveListener); mSave.setEnabled(false); + mProgress = (ProgressBar) view.findViewById(android.R.id.progress); + return view; } @@ -92,7 +96,6 @@ public class SaveFragment extends Fragment { if (mIgnoreNextEdit) { mIgnoreNextEdit = false; } else { - Log.d(TAG, "onTextChanged!"); mReplaceTarget = null; } } @@ -140,4 +143,9 @@ public class SaveFragment extends Fragment { public void setSaveEnabled(boolean enabled) { mSave.setEnabled(enabled); } + + public void setPending(boolean pending) { + mSave.setVisibility(pending ? View.INVISIBLE : View.VISIBLE); + mProgress.setVisibility(pending ? View.VISIBLE : View.GONE); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java index 19ad2e2..a23dd15 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java @@ -19,6 +19,8 @@ package com.android.documentsui; 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.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.database.AbstractCursor; import android.database.Cursor; @@ -62,10 +64,9 @@ public class SortingCursorWrapper extends AbstractCursor { switch (sortOrder) { case SORT_ORDER_DISPLAY_NAME: - final String mimeType = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); - final String displayName = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String displayName = getCursorString( + cursor, Document.COLUMN_DISPLAY_NAME); if (Document.MIME_TYPE_DIR.equals(mimeType)) { mValueString[i] = '\001' + displayName; } else { @@ -73,11 +74,10 @@ public class SortingCursorWrapper extends AbstractCursor { } break; case SORT_ORDER_LAST_MODIFIED: - mValueLong[i] = cursor.getLong( - cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); + mValueLong[i] = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); break; case SORT_ORDER_SIZE: - mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE)); + mValueLong[i] = getCursorLong(cursor, Document.COLUMN_SIZE); break; } } |