Add Feature Flag support.
Bug: 35923154
Change-Id: I34dd956e8309c779e7a3f35eadc3d84132b970ce
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index cde116b..3897dc3 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -32,18 +32,17 @@
import com.android.documentsui.AbstractActionHandler.CommonAddons;
import com.android.documentsui.LoadDocStackTask.LoadDocStackCallback;
-import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.Lookup;
+import com.android.documentsui.base.Providers;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
-import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.AnimationView;
+import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.files.LauncherActivity;
@@ -58,7 +57,6 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
import javax.annotation.Nullable;
@@ -132,8 +130,13 @@
@Override
public void refreshDocument(DocumentInfo doc, BooleanConsumer callback) {
- RefreshTask task = new RefreshTask(mState, doc, REFRESH_SPINNER_TIMEOUT,
- mActivity.getApplicationContext(), mActivity::isDestroyed,
+ RefreshTask task = new RefreshTask(
+ mInjector.features,
+ mState,
+ doc,
+ REFRESH_SPINNER_TIMEOUT,
+ mActivity.getApplicationContext(),
+ mActivity::isDestroyed,
callback);
task.executeOnExecutor(mExecutors.lookup(doc == null ? null : doc.authority));
}
@@ -345,7 +348,7 @@
protected final boolean launchToDocument(Uri uri) {
// We don't support launching to a document in an archive.
- if (!ArchivesProvider.AUTHORITY.equals(uri.getAuthority())) {
+ if (!Providers.isArchiveUri(uri)) {
loadDocument(uri, this::onStackLoaded);
return true;
}
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 5c93f76..0a4dff1 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -16,10 +16,13 @@
package com.android.documentsui;
+import static com.android.documentsui.base.Shared.VERBOSE;
+
import android.content.AsyncTaskLoader;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -36,7 +39,6 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.FilteringCursorWrapper;
import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.sorting.SortModel;
@@ -100,7 +102,8 @@
}
result.client = client;
- if (Shared.ENABLE_OMC_API_FEATURES) {
+ Resources resources = getContext().getResources();
+ if (resources.getBoolean(R.bool.feature_content_paging)) {
Bundle queryArgs = new Bundle();
mModel.addQuerySortArgs(queryArgs);
@@ -122,12 +125,19 @@
cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
- if (mSearchMode && !Shared.ENABLE_OMC_API_FEATURES) {
+ if (mSearchMode && !resources.getBoolean(R.bool.feature_folders_in_search_results)) {
// There is no findDocumentPath API. Enable filtering on folders in search mode.
cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
}
- cursor = mModel.sortCursor(cursor);
+ // TODO: When API tweaks have landed, use ContentResolver.EXTRA_HONORED_ARGS
+ // instead of checking directly for ContentResolver.QUERY_ARG_SORT_COLUMNS (won't work)
+ if (resources.getBoolean(R.bool.feature_content_paging)
+ && cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
+ if (VERBOSE) Log.d(TAG, "Skipping sort of pre-sorted cursor. Booya!");
+ } else {
+ cursor = mModel.sortCursor(cursor);
+ }
result.cursor = cursor;
} catch (Exception e) {
Log.w(TAG, "Failed to query", e);
diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index ecc4251..8126e4d 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -18,6 +18,7 @@
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import static com.android.documentsui.base.Shared.DEBUG;
+import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.ColorRes;
import android.annotation.Nullable;
@@ -41,8 +42,8 @@
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Events;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.Procedure;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.dirlist.DocumentHolder;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.FocusHandler;
@@ -60,6 +61,7 @@
private final ContentScope mScope = new ContentScope();
+ private final Features mFeatures;
private final SelectionManager mSelectionMgr;
private final DrawerController mDrawer;
private final Procedure mRootsFocuser;
@@ -68,11 +70,13 @@
private boolean mNavDrawerHasFocus;
public FocusManager(
+ Features features,
SelectionManager selectionMgr,
DrawerController drawer,
Procedure rootsFocuser,
@ColorRes int color) {
+ mFeatures = checkNotNull(features);
mSelectionMgr = selectionMgr;
mDrawer = drawer;
mRootsFocuser = rootsFocuser;
@@ -84,7 +88,7 @@
public boolean advanceFocusArea() {
// This should only be called in pre-O devices.
// O has built-in keyboard navigation support.
- assert(!Shared.ENABLE_OMC_API_FEATURES);
+ assert(!mFeatures.isSystemKeyboardNavigationEnabled());
boolean focusChanged = false;
if (mNavDrawerHasFocus) {
mDrawer.setOpen(false);
diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java
index 7474cbe..0a3f5bb 100644
--- a/src/com/android/documentsui/Injector.java
+++ b/src/com/android/documentsui/Injector.java
@@ -25,6 +25,7 @@
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.base.EventHandler;
+import com.android.documentsui.base.Features;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.prefs.ScopedPreferences;
@@ -41,6 +42,7 @@
*/
public class Injector<T extends ActionHandler> {
+ public final Features features;
public final ActivityConfig config;
public final ScopedPreferences prefs;
public final MessageBuilder messages;
@@ -60,21 +62,24 @@
@ContentScoped
public SelectionManager selectionMgr;
- private final Model mModel = new Model();
- private DocumentsAdapter mAdapter;
+ private final Model mModel;
// must be initialized before calling super.onCreate because prefs
// are used in State initialization.
public Injector(
+ Features features,
ActivityConfig config,
ScopedPreferences prefs,
MessageBuilder messages,
DialogController dialogs) {
+ this.features = features;
this.config = config;
this.prefs = prefs;
this.messages = messages;
this.dialogs = dialogs;
+
+ mModel = new Model(this.features);
}
public Model getModel() {
diff --git a/src/com/android/documentsui/LoadDocStackTask.java b/src/com/android/documentsui/LoadDocStackTask.java
index 712f5e1..7d2eb38 100644
--- a/src/com/android/documentsui/LoadDocStackTask.java
+++ b/src/com/android/documentsui/LoadDocStackTask.java
@@ -25,9 +25,9 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.PairedTask;
import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.roots.RootsAccess;
import java.util.List;
@@ -61,7 +61,8 @@
@Override
public @Nullable DocumentStack run(Uri... uris) {
- if (Shared.ENABLE_OMC_API_FEATURES && mDocs.isDocumentUri(uris[0])) {
+ // assert(Features.OMC_RUNTIME);
+ if (mDocs.isDocumentUri(uris[0])) {
final Uri docUri;
if (DocumentsContract.isTreeUri(uris[0])) {
// Reconstruct tree URI into a plain document URI so that we can get the full path
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index ead5e8f..e5827b3 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -33,9 +33,9 @@
import android.text.format.DateUtils;
import android.util.Log;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.FilteringCursorWrapper;
import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.roots.RootsAccess;
@@ -83,6 +83,7 @@
private final RootsAccess mRoots;
private final State mState;
+ private final Features mFeatures;
@GuardedBy("mTasks")
/** A authority -> RecentsTask map */
@@ -93,10 +94,11 @@
private DirectoryResult mResult;
- public RecentsLoader(Context context, RootsAccess roots, State state) {
+ public RecentsLoader(Context context, RootsAccess roots, State state, Features features) {
super(context);
mRoots = roots;
mState = state;
+ mFeatures = features;
// Keep clients around on high-RAM devices, since we'd be spinning them
// up moments later to fetch thumbnails anyway.
@@ -330,7 +332,7 @@
final Uri uri =
DocumentsContract.buildRecentDocumentsUri(authority, rootIds.get(i));
try {
- if (Shared.ENABLE_OMC_API_FEATURES) {
+ if (mFeatures.isContentPagingEnabled()) {
final Bundle queryArgs = new Bundle();
mState.sortModel.addQuerySortArgs(queryArgs);
res[i] = client.query(uri, null, queryArgs, null);
diff --git a/src/com/android/documentsui/RefreshTask.java b/src/com/android/documentsui/RefreshTask.java
index ef6a16e..deaf0ca 100644
--- a/src/com/android/documentsui/RefreshTask.java
+++ b/src/com/android/documentsui/RefreshTask.java
@@ -19,8 +19,6 @@
import static com.android.documentsui.base.Shared.DEBUG;
import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.Fragment;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,7 +30,7 @@
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.CheckedTask;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.Shared;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
/**
@@ -45,17 +43,20 @@
private final static String TAG = "RefreshTask";
private final @ApplicationScope Context mContext;
+ private final Features mFeatures;
private final State mState;
private final DocumentInfo mDoc;
private final BooleanConsumer mCallback;
private final CancellationSignal mSignal;
- public RefreshTask(State state, DocumentInfo doc, long timeout,
+
+ public RefreshTask(Features features, State state, DocumentInfo doc, long timeout,
@ApplicationScope Context context, Check check, BooleanConsumer callback) {
super(check);
+ mFeatures = features;
+ mState = state;
mDoc = doc;
mContext = context;
- mState = state;
mCallback = callback;
mSignal = new CancellationSignal();
setTimeout(timeout);
@@ -82,7 +83,7 @@
// supports it, the ContentProvider will automatically send a content updated notification
// and we will update accordingly. Else, we just tell the callback that Refresh is not
// supported.
- if (!Shared.ENABLE_OMC_API_FEATURES) {
+ if (!mFeatures.isContentRefreshEnabled()) {
Log.w(TAG, "Ignoring attempt to call Refresh on an older Android platform.");
return false;
}
diff --git a/src/com/android/documentsui/SharedInputHandler.java b/src/com/android/documentsui/SharedInputHandler.java
index c5417ea..3678059 100644
--- a/src/com/android/documentsui/SharedInputHandler.java
+++ b/src/com/android/documentsui/SharedInputHandler.java
@@ -18,17 +18,19 @@
import android.view.KeyEvent;
import com.android.documentsui.base.Events;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.Procedure;
-import com.android.documentsui.base.Shared;
public class SharedInputHandler {
private final FocusManager mFocusManager;
- private Procedure mDirPopper;
+ private final Procedure mDirPopper;
+ private final Features mFeatures;
- public SharedInputHandler(FocusManager focusManager, Procedure dirPopper) {
+ public SharedInputHandler(FocusManager focusManager, Procedure dirPopper, Features features) {
mFocusManager = focusManager;
mDirPopper = dirPopper;
+ mFeatures = features;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
@@ -38,7 +40,8 @@
// which is probably what the user is trying to do.
mFocusManager.focusDirectoryList();
return true;
- } else if (keyCode == KeyEvent.KEYCODE_TAB && !Shared.ENABLE_OMC_API_FEATURES) {
+ } else if (keyCode == KeyEvent.KEYCODE_TAB
+ && !mFeatures.isSystemKeyboardNavigationEnabled()) {
// Tab toggles focus on the navigation drawer.
// This should only be called in pre-O devices, since O has built-in keyboard navigation
// support.
diff --git a/src/com/android/documentsui/base/DebugFlags.java b/src/com/android/documentsui/base/DebugFlags.java
index e131d8e..2298e76 100644
--- a/src/com/android/documentsui/base/DebugFlags.java
+++ b/src/com/android/documentsui/base/DebugFlags.java
@@ -65,7 +65,6 @@
}
public static boolean addForcedPagingArgs(Bundle queryArgs) {
- assert(Shared.ENABLE_OMC_API_FEATURES);
boolean flagsAdded = false;
if (sForcedPageOffset >= 0) {
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, sForcedPageOffset);
diff --git a/src/com/android/documentsui/base/DocumentFilters.java b/src/com/android/documentsui/base/DocumentFilters.java
new file mode 100644
index 0000000..de8085b
--- /dev/null
+++ b/src/com/android/documentsui/base/DocumentFilters.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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.base;
+
+import static com.android.documentsui.base.DocumentInfo.getCursorInt;
+import static com.android.documentsui.base.DocumentInfo.getCursorString;
+
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+
+import com.android.documentsui.archives.ArchivesProvider;
+import com.android.documentsui.roots.RootCursorWrapper;
+
+import java.util.function.Predicate;
+
+/**
+ * Predicates for matching certain types of document records
+ * in {@link Cursor}s.
+ */
+public final class DocumentFilters {
+
+ public static final Predicate<Cursor> ANY = (Cursor c) -> { return true; };
+ public static final Predicate<Cursor> VIRTUAL = DocumentFilters::isVirtual;
+ private static final Predicate<Cursor> O_SHARABLE = DocumentFilters::isSharableInO;
+ private static final Predicate<Cursor> PREO_SHARABLE = DocumentFilters::isSharablePreO;
+
+ public static Predicate<Cursor> sharable(Features features) {
+ return features.isVirtualFilesSharingEnabled()
+ ? DocumentFilters.O_SHARABLE
+ : DocumentFilters.PREO_SHARABLE;
+ }
+
+ private static boolean isSharableInO(Cursor c) {
+ int flags = getCursorInt(c, Document.COLUMN_FLAGS);
+ String authority = getCursorString(c, RootCursorWrapper.COLUMN_AUTHORITY);
+
+ return (flags & Document.FLAG_PARTIAL) == 0
+ && !ArchivesProvider.AUTHORITY.equals(authority);
+ }
+
+ /**
+ * Filter that passes (returns true) for all files which can be shared in O.
+ */
+ private static boolean isSharablePreO(Cursor c) {
+ int flags = getCursorInt(c, Document.COLUMN_FLAGS);
+ String authority = getCursorString(c, RootCursorWrapper.COLUMN_AUTHORITY);
+
+ return (flags & Document.FLAG_PARTIAL) == 0
+ && (flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0
+ && !ArchivesProvider.AUTHORITY.equals(authority);
+ }
+
+ /**
+ * Filter that passes (returns true) only virtual documents.
+ */
+ private static final boolean isVirtual(Cursor c) {
+ int flags = getCursorInt(c, Document.COLUMN_FLAGS);
+ return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+ }
+}
diff --git a/src/com/android/documentsui/base/Features.java b/src/com/android/documentsui/base/Features.java
new file mode 100644
index 0000000..836d03a
--- /dev/null
+++ b/src/com/android/documentsui/base/Features.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.base;
+
+import android.content.res.Resources;
+
+import com.android.documentsui.R;
+
+/**
+ * Provides access to feature flags configured in config.xml.
+ */
+public interface Features {
+
+ // technically we want to check >= O, but we'd need to patch back the O version code :|
+ public static final boolean OMC_RUNTIME =
+ android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.N_MR1;
+
+ boolean isArchiveCreationEnabled();
+ boolean isRemoteActionsEnabled();
+ boolean isContentPagingEnabled();
+ boolean isFoldersInSearchResultsEnabled();
+ boolean isSystemKeyboardNavigationEnabled();
+ boolean isLaunchToDocumentEnabled();
+ boolean isVirtualFilesSharingEnabled();
+ boolean isContentRefreshEnabled();
+
+ public static Features create(Resources resources) {
+ return new RuntimeFeatures(resources);
+ }
+
+ final class RuntimeFeatures implements Features {
+ private final Resources mRes;
+
+ public RuntimeFeatures(Resources resources) {
+ mRes = resources;
+ }
+
+ @Override
+ public boolean isArchiveCreationEnabled() {
+ return mRes.getBoolean(R.bool.feature_archive_creation);
+ }
+
+ @Override
+ public boolean isRemoteActionsEnabled() {
+ return mRes.getBoolean(R.bool.feature_remote_actions);
+ }
+
+ @Override
+ public boolean isContentPagingEnabled() {
+ return mRes.getBoolean(R.bool.feature_content_paging);
+ }
+
+ @Override
+ public boolean isFoldersInSearchResultsEnabled() {
+ return mRes.getBoolean(R.bool.feature_folders_in_search_results);
+ }
+
+ @Override
+ public boolean isSystemKeyboardNavigationEnabled() {
+ return mRes.getBoolean(R.bool.feature_system_keyboard_navigation);
+ }
+
+ @Override
+ public boolean isLaunchToDocumentEnabled() {
+ return mRes.getBoolean(R.bool.feature_launch_to_document);
+ }
+
+ @Override
+ public boolean isContentRefreshEnabled() {
+ return mRes.getBoolean(R.bool.feature_content_refresh);
+ }
+
+ @Override
+ public boolean isVirtualFilesSharingEnabled() {
+ return mRes.getBoolean(R.bool.feature_virtual_files_sharing);
+ }
+ }
+}
diff --git a/src/com/android/documentsui/base/Providers.java b/src/com/android/documentsui/base/Providers.java
index 561c442..9f825dc 100644
--- a/src/com/android/documentsui/base/Providers.java
+++ b/src/com/android/documentsui/base/Providers.java
@@ -15,6 +15,10 @@
*/
package com.android.documentsui.base;
+import android.net.Uri;
+
+import com.android.documentsui.archives.ArchivesProvider;
+
/**
* Details about various system providers. These all need to be in sync with the
* resources in their respective packages.
@@ -34,4 +38,8 @@
public static final String ROOT_ID_AUDIO = "audio_root";
public static final String AUTHORITY_MTP = "com.android.mtp.documents";
+
+ public static boolean isArchiveUri(Uri uri) {
+ return uri != null && ArchivesProvider.AUTHORITY.equals(uri.getAuthority());
+ }
}
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index fa228d1..7616edb 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -51,8 +51,6 @@
public static final boolean DEBUG = true;
public static final boolean VERBOSE = DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
- public static final boolean ENABLE_OMC_API_FEATURES = true;
-
/** Intent action name to pick a copy destination. */
public static final String ACTION_PICK_COPY_DESTINATION =
"com.android.documentsui.PICK_COPY_DESTINATION";
diff --git a/src/com/android/documentsui/clipping/DocumentClipper.java b/src/com/android/documentsui/clipping/DocumentClipper.java
index 2ec65d1..d0dfc7c 100644
--- a/src/com/android/documentsui/clipping/DocumentClipper.java
+++ b/src/com/android/documentsui/clipping/DocumentClipper.java
@@ -29,6 +29,7 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.selection.Selection;
@@ -337,7 +338,8 @@
private static ClipData createClipData(
ClipDescription description, ArrayList<ClipData.Item> clipItems) {
- if (Shared.ENABLE_OMC_API_FEATURES) {
+ // technically we want to check >= O, but we'd need to patch back the O version code :|
+ if (Features.OMC_RUNTIME) {
return new ClipData(description, clipItems);
}
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 908205c..57facaf3 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -86,6 +86,7 @@
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.base.Events.MotionInputEvent;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
@@ -111,7 +112,6 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
@@ -1176,6 +1176,11 @@
private final class AdapterEnvironment implements DocumentsAdapter.Environment {
@Override
+ public Features getFeatures() {
+ return mInjector.features;
+ }
+
+ @Override
public Context getContext() {
return mActivity;
}
@@ -1260,7 +1265,7 @@
case TYPE_RECENT_OPEN:
if (DEBUG) Log.d(TAG, "Creating new loader recents.");
final RootsAccess roots = DocumentsApplication.getRootsCache(context);
- return new RecentsLoader(context, roots, mState);
+ return new RecentsLoader(context, roots, mState, mInjector.features);
default:
throw new IllegalStateException("Unknown type " + mLocalState.mType);
diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 8d7c63c..9b1794c 100644
--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -25,6 +25,7 @@
import android.support.v7.widget.RecyclerView;
import com.android.documentsui.base.EventListener;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import java.util.List;
@@ -100,6 +101,7 @@
*/
interface Environment {
Context getContext();
+ Features getFeatures();
int getColumnCount();
State getDisplayState();
boolean isInSearchMode();
diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java
index 7f9c6aa..d8044bb 100644
--- a/src/com/android/documentsui/dirlist/Message.java
+++ b/src/com/android/documentsui/dirlist/Message.java
@@ -115,7 +115,8 @@
}
private void updateToRecoverableExceptionHeader(Update event) {
- assert(Shared.ENABLE_OMC_API_FEATURES);
+ assert(mEnv.getFeatures().isRemoteActionsEnabled());
+
RootInfo root = mEnv.getDisplayState().stack.getRoot();
update(mEnv.getContext().getResources().getText(R.string.authentication_required),
mEnv.getContext().getString(R.string.open_app, root.title),
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index 3de9c73..43aed9a 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -16,10 +16,8 @@
package com.android.documentsui.dirlist;
-import static com.android.documentsui.base.DocumentInfo.getCursorInt;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.base.Shared.ENABLE_OMC_API_FEATURES;
import static com.android.documentsui.base.Shared.VERBOSE;
import android.annotation.IntDef;
@@ -35,10 +33,10 @@
import android.util.Log;
import com.android.documentsui.DirectoryResult;
-import com.android.documentsui.archives.ArchivesProvider;
+import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventListener;
-import com.android.documentsui.base.Shared;
+import com.android.documentsui.base.Features;
import com.android.documentsui.roots.RootCursorWrapper;
import com.android.documentsui.selection.Selection;
@@ -58,46 +56,26 @@
@VisibleForTesting
public class Model {
- /**
- * Filter that passes (returns true) for all files which can be shared.
- */
- public static final Predicate<Cursor> SHARABLE_FILE_FILTER = (Cursor c) -> {
- int flags = getCursorInt(c, Document.COLUMN_FLAGS);
- String authority = getCursorString(c, RootCursorWrapper.COLUMN_AUTHORITY);
- if (!ENABLE_OMC_API_FEATURES) {
- return (flags & Document.FLAG_PARTIAL) == 0
- && (flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0
- && !ArchivesProvider.AUTHORITY.equals(authority);
- }
- return (flags & Document.FLAG_PARTIAL) == 0
- && !ArchivesProvider.AUTHORITY.equals(authority);
- };
-
- /**
- * Filter that passes (returns true) only virtual documents.
- */
- public static final Predicate<Cursor> VIRTUAL_DOCUMENT_FILTER = (Cursor c) -> {
- int flags = getCursorInt(c, Document.COLUMN_FLAGS);
- return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
- };
-
- private static final Predicate<Cursor> ANY_FILE_FILTER = (Cursor c) -> true;
-
private static final String TAG = "Model";
+ @Nullable String info;
+ @Nullable String error;
+ @Nullable DocumentInfo doc;
+
+ private final Features mFeatures;
/** Maps Model ID to cursor positions, for looking up items by Model ID. */
private final Map<String, Integer> mPositions = new HashMap<>();
private final Set<String> mFileNames = new HashSet<>();
private boolean mIsLoading;
private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
- @Nullable private Cursor mCursor;
+ private @Nullable Cursor mCursor;
private int mCursorCount;
private String mIds[] = new String[0];
- @Nullable String info;
- @Nullable String error;
- @Nullable DocumentInfo doc;
+ public Model(Features features) {
+ mFeatures = features;
+ }
public void addUpdateListener(EventListener<Update> listener) {
mUpdateListeners.add(listener);
@@ -114,7 +92,7 @@
}
private void notifyUpdateListeners(Exception e) {
- Update error = new Update(e);
+ Update error = new Update(e, mFeatures.isRemoteActionsEnabled());
for (EventListener<Update> handler: mUpdateListeners) {
handler.accept(error);
}
@@ -227,7 +205,7 @@
}
public List<DocumentInfo> getDocuments(Selection selection) {
- return loadDocuments(selection, ANY_FILE_FILTER);
+ return loadDocuments(selection, DocumentFilters.ANY);
}
public @Nullable DocumentInfo getDocument(String modelId) {
@@ -307,16 +285,19 @@
private final @UpdateType int mUpdateType;
private final @Nullable Exception mException;
+ private final boolean mRemoteActionEnabled;
private Update() {
mUpdateType = TYPE_UPDATE;
mException = null;
+ mRemoteActionEnabled = false;
}
- public Update(Exception exception) {
+ public Update(Exception exception, boolean remoteActionsEnabled) {
assert(exception != null);
mUpdateType = TYPE_UPDATE_EXCEPTION;
mException = exception;
+ mRemoteActionEnabled = remoteActionsEnabled;
}
public boolean isUpdate() {
@@ -328,7 +309,8 @@
}
public boolean hasRecoverableException() {
- return Shared.ENABLE_OMC_API_FEATURES && hasException()
+ return mRemoteActionEnabled
+ && hasException()
&& mException instanceof RecoverableSecurityException;
}
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 602446a..5a7f374 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -17,7 +17,6 @@
package com.android.documentsui.files;
import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.base.Shared.ENABLE_OMC_API_FEATURES;
import android.app.Activity;
import android.content.ActivityNotFoundException;
@@ -27,8 +26,6 @@
import android.content.Intent;
import android.net.Uri;
import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsContract.Document;
import android.util.Log;
import com.android.documentsui.AbstractActionHandler;
@@ -41,8 +38,10 @@
import com.android.documentsui.R;
import com.android.documentsui.base.ConfirmationCallback;
import com.android.documentsui.base.ConfirmationCallback.Result;
+import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.MimeTypes;
import com.android.documentsui.base.RootInfo;
@@ -53,14 +52,12 @@
import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.files.ActionHandler.Addons;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.GetRootDocumentTask;
import com.android.documentsui.roots.RootsAccess;
import com.android.documentsui.selection.Selection;
-import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperations;
@@ -81,8 +78,9 @@
private static final String TAG = "ManagerActionHandler";
private final ActionModeAddons mActionModeAddons;
+ private final Features mFeatures;
+ private final ActivityConfig mConfig;
private final DialogController mDialogs;
- private final ActivityConfig mActConfig;
private final DocumentClipper mClipper;
private final ClipStore mClipStore;
private @Nullable Model mModel;
@@ -102,8 +100,9 @@
super(activity, state, roots, docs, searchMgr, executors, injector);
mActionModeAddons = actionModeAddons;
+ mFeatures = injector.features;
+ mConfig = injector.config;
mDialogs = injector.dialogs;
- mActConfig = injector.config;
mClipper = clipper;
mClipStore = clipStore;
}
@@ -187,7 +186,7 @@
return false;
}
- if (mActConfig.isDocumentEnabled(doc.mimeType, doc.flags, mState)) {
+ if (mConfig.isDocumentEnabled(doc.mimeType, doc.flags, mState)) {
onDocumentPicked(doc);
mSelectionMgr.clearSelection();
return true;
@@ -314,8 +313,8 @@
assert(!selection.isEmpty());
// Model must be accessed in UI thread, since underlying cursor is not threadsafe.
- List<DocumentInfo> docs =
- mModel.loadDocuments(selection, Model.SHARABLE_FILE_FILTER);
+ List<DocumentInfo> docs = mModel.loadDocuments(
+ selection, DocumentFilters.sharable(mFeatures));
Intent intent;
@@ -346,8 +345,8 @@
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- if (Shared.ENABLE_OMC_API_FEATURES
- && mModel.hasDocuments(selection, Model.VIRTUAL_DOCUMENT_FILTER)) {
+ if (mFeatures.isVirtualFilesSharingEnabled()
+ && mModel.hasDocuments(selection, DocumentFilters.VIRTUAL)) {
intent.addCategory(Intent.CATEGORY_TYPED_OPENABLE);
}
@@ -420,8 +419,9 @@
private boolean launchToRoot(Intent intent) {
String action = intent.getAction();
+ // TODO: Remove the "BROWSE" action once our min runtime in O.
if (Intent.ACTION_VIEW.equals(action)
- || (!ENABLE_OMC_API_FEATURES && "android.provider.action.BROWSE".equals(action))) {
+ || "android.provider.action.BROWSE".equals(action)) {
Uri uri = intent.getData();
if (DocumentsContract.isRootUri(mActivity, uri)) {
if (DEBUG) Log.d(TAG, "Launching with root URI.");
@@ -455,7 +455,7 @@
}
Intent intent = Intent.createChooser(buildViewIntent(doc), null);
- if (Shared.ENABLE_OMC_API_FEATURES) {
+ if (Features.OMC_RUNTIME) {
intent.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
}
try {
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 5f823ba..b5989b2 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -46,6 +46,7 @@
import com.android.documentsui.SharedInputHandler;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
@@ -85,6 +86,7 @@
MessageBuilder messages = new MessageBuilder(this);
mInjector = new Injector<>(
+ Features.create(getResources()),
new Config(),
ScopedPreferences.create(this, PREFERENCES_SCOPE),
messages,
@@ -96,6 +98,7 @@
mInjector.selectionMgr = new SelectionManager(SelectionManager.MODE_MULTIPLE);
mInjector.focusManager = new FocusManager(
+ mInjector.features,
mInjector.selectionMgr,
mDrawer,
this::focusSidebar,
@@ -133,7 +136,8 @@
mActivityInputHandler =
new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments);
- mSharedInputHandler = new SharedInputHandler(mInjector.focusManager, this::popDir);
+ mSharedInputHandler =
+ new SharedInputHandler(mInjector.focusManager, this::popDir, mInjector.features);
RootsFragment.show(getFragmentManager(), null);
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 8a21b48..a92685a 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -17,8 +17,6 @@
package com.android.documentsui.picker;
import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
-import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
import android.app.Activity;
import android.content.Intent;
@@ -35,21 +33,16 @@
import com.android.documentsui.Metrics;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
-import com.android.documentsui.base.EventListener;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.Lookup;
-import com.android.documentsui.base.MimeTypes;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
-import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
-import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.Model.Update;
import com.android.documentsui.picker.ActionHandler.Addons;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.RootsAccess;
-import com.android.documentsui.selection.SelectionManager;
import java.util.concurrent.Executor;
@@ -62,6 +55,7 @@
private static final String TAG = "PickerActionHandler";
+ private final Features mFeatures;
private final ActivityConfig mConfig;
private @Nullable Model mModel;
@@ -77,6 +71,7 @@
super(activity, state, roots, docs, searchMgr, executors, injector);
mConfig = injector.config;
+ mFeatures = injector.features;
}
@Override
@@ -99,7 +94,7 @@
return;
}
- if (Shared.ENABLE_OMC_API_FEATURES && launchToDocument(intent)) {
+ if (mFeatures.isLaunchToDocumentEnabled() && launchToDocument(intent)) {
if (DEBUG) Log.d(TAG, "Launched to a document.");
return;
}
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 315f432..40c307a 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -53,6 +53,7 @@
import com.android.documentsui.R;
import com.android.documentsui.SharedInputHandler;
import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.Features;
import com.android.documentsui.base.MimeTypes;
import com.android.documentsui.base.PairedTask;
import com.android.documentsui.base.RootInfo;
@@ -89,6 +90,7 @@
public void onCreate(Bundle icicle) {
mInjector = new Injector<>(
+ Features.create(getResources()),
new Config(),
ScopedPreferences.create(this, PREFERENCES_SCOPE),
new MessageBuilder(this),
@@ -102,6 +104,7 @@
: SelectionManager.MODE_SINGLE);
mInjector.focusManager = new FocusManager(
+ mInjector.features,
mInjector.selectionMgr,
mDrawer,
this::focusSidebar,
@@ -124,8 +127,8 @@
Intent intent = getIntent();
- mSharedInputHandler = new SharedInputHandler(mInjector.focusManager, this::popDir);
-
+ mSharedInputHandler =
+ new SharedInputHandler(mInjector.focusManager, this::popDir, mInjector.features);
setupLayout(intent);
mInjector.actions.initLocation(intent);
}
diff --git a/src/com/android/documentsui/prefs/ScopedPreferences.java b/src/com/android/documentsui/prefs/ScopedPreferences.java
index fd0f0fe..930927d 100644
--- a/src/com/android/documentsui/prefs/ScopedPreferences.java
+++ b/src/com/android/documentsui/prefs/ScopedPreferences.java
@@ -76,7 +76,7 @@
@Override
public boolean getEnableArchiveCreation() {
- final boolean defaultValue = mResources.getBoolean(R.bool.enable_archive_creation);
+ final boolean defaultValue = mResources.getBoolean(R.bool.feature_archive_creation);
return mSharedPrefs.getBoolean(ENABLE_ARCHIVE_CREATION + mScope, defaultValue);
}
diff --git a/src/com/android/documentsui/queries/DebugCommandProcessor.java b/src/com/android/documentsui/queries/DebugCommandProcessor.java
index ff62526..7b36d5b 100644
--- a/src/com/android/documentsui/queries/DebugCommandProcessor.java
+++ b/src/com/android/documentsui/queries/DebugCommandProcessor.java
@@ -24,7 +24,6 @@
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.EventHandler;
-import com.android.documentsui.base.Shared;
import java.util.ArrayList;
import java.util.List;
@@ -111,11 +110,6 @@
}
private static boolean forcePaging(String[] tokens) {
- if (!Shared.ENABLE_OMC_API_FEATURES) {
- Log.i(TAG, "Paging is disabled.");
- return false;
- }
-
if ("page".equals(tokens[0])) {
if (tokens.length >= 2) {
try {
diff --git a/src/com/android/documentsui/sidebar/RootsList.java b/src/com/android/documentsui/sidebar/RootsList.java
index 1de2710..b83fcbf 100644
--- a/src/com/android/documentsui/sidebar/RootsList.java
+++ b/src/com/android/documentsui/sidebar/RootsList.java
@@ -20,7 +20,7 @@
import android.view.KeyEvent;
import android.widget.ListView;
-import com.android.documentsui.base.Shared;
+import com.android.documentsui.base.Features;
/**
* The list in the navigation drawer. This class exists for the purpose of overriding the key
@@ -29,22 +29,28 @@
*/
public class RootsList extends ListView {
+ private final Features mFeatures;
+
// Multiple constructors are needed to handle all the different ways this View could be
// constructed by the framework. Don't remove them!
public RootsList(Context context) {
super(context);
+ mFeatures = Features.create(getResources());
}
public RootsList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mFeatures = Features.create(getResources());
}
public RootsList(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mFeatures = Features.create(getResources());
}
public RootsList(Context context, AttributeSet attrs) {
super(context, attrs);
+ mFeatures = Features.create(getResources());
}
@Override
@@ -53,7 +59,8 @@
// Ignore tab key events - this causes them to bubble up to the global key handler where
// they are appropriately handled. See BaseActivity.onKeyDown.
case KeyEvent.KEYCODE_TAB:
- return Shared.ENABLE_OMC_API_FEATURES ? super.onKeyDown(keyCode, event) : false;
+ return mFeatures.isSystemKeyboardNavigationEnabled()
+ && super.onKeyDown(keyCode, event);
// Prevent left/right arrow keystrokes from shifting focus away from the roots list.
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
diff --git a/src/com/android/documentsui/sorting/SortModel.java b/src/com/android/documentsui/sorting/SortModel.java
index e3eb1e9..a61543f 100644
--- a/src/com/android/documentsui/sorting/SortModel.java
+++ b/src/com/android/documentsui/sorting/SortModel.java
@@ -17,8 +17,6 @@
package com.android.documentsui.sorting;
import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.base.Shared.ENABLE_OMC_API_FEATURES;
-import static com.android.documentsui.base.Shared.VERBOSE;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -33,7 +31,6 @@
import android.view.View;
import com.android.documentsui.R;
-import com.android.documentsui.base.Shared;
import com.android.documentsui.sorting.SortDimension.SortDirection;
import java.lang.annotation.Retention;
@@ -225,15 +222,6 @@
}
public Cursor sortCursor(Cursor cursor) {
- if (ENABLE_OMC_API_FEATURES
- && cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
- if (VERBOSE) Log.i(TAG, "Cursor is pre-sorted by provider. Skipping sort. Booya!");
-
- // TODO: assert that the contents of QUERY_ARG_SORT_COLUMNS
- // matches the sort dimension...once we're returning any pre-sorted results.
- return cursor;
- }
-
if (mSortedDimension != null) {
return new SortingCursorWrapper(cursor, mSortedDimension);
} else {
@@ -242,7 +230,7 @@
}
public void addQuerySortArgs(Bundle queryArgs) {
- assert(Shared.ENABLE_OMC_API_FEATURES);
+ // should only be called when R.bool.feature_content_paging is true
final int id = getSortedDimensionId();
switch (id) {
@@ -287,7 +275,12 @@
}
public @Nullable String getDocumentSortQuery() {
- assert(!Shared.ENABLE_OMC_API_FEATURES);
+ // This method should only be called when R.bool.feature_content_paging is false.
+ // Once that feature is enabled by default, this method should be removed.
+ // The following assert exists simply to make reference to the resource id
+ // so the compiler will fail when the feature is removed...reminding you and me
+ // to remove this method :)
+ assert(R.bool.feature_content_paging != Integer.MIN_VALUE);
final int id = getSortedDimensionId();
final String columnName;