summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--res/values/strings.xml3
-rw-r--r--src/com/android/documentsui/AbstractActionHandler.java4
-rw-r--r--src/com/android/documentsui/ActionHandler.java2
-rw-r--r--src/com/android/documentsui/BaseActivity.java2
-rw-r--r--src/com/android/documentsui/CrossProfileException.java23
-rw-r--r--src/com/android/documentsui/CrossProfileNoPermissionException.java23
-rw-r--r--src/com/android/documentsui/CrossProfileQuietModeException.java23
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java34
-rw-r--r--src/com/android/documentsui/DocumentsAccess.java29
-rw-r--r--src/com/android/documentsui/Model.java6
-rw-r--r--src/com/android/documentsui/MultiRootDocumentsLoader.java3
-rw-r--r--src/com/android/documentsui/RecentsLoader.java14
-rw-r--r--src/com/android/documentsui/RefreshTask.java8
-rw-r--r--src/com/android/documentsui/base/State.java7
-rw-r--r--src/com/android/documentsui/base/UserId.java9
-rw-r--r--src/com/android/documentsui/dirlist/AppsRowItemData.java2
-rw-r--r--src/com/android/documentsui/dirlist/Message.java9
-rw-r--r--src/com/android/documentsui/picker/ActionHandler.java49
-rw-r--r--src/com/android/documentsui/picker/PickActivity.java26
-rw-r--r--src/com/android/documentsui/sidebar/AppItem.java2
-rw-r--r--src/com/android/documentsui/sidebar/RootAndAppItem.java2
-rw-r--r--src/com/android/documentsui/sidebar/RootsFragment.java4
-rw-r--r--src/com/android/documentsui/ui/DialogController.java8
-rw-r--r--tests/common/com/android/documentsui/TestActivity.java16
-rw-r--r--tests/common/com/android/documentsui/TestUserIdManager.java43
-rw-r--r--tests/common/com/android/documentsui/testing/UserManagers.java36
-rw-r--r--tests/common/com/android/documentsui/ui/TestDialogController.java14
-rw-r--r--tests/unit/com/android/documentsui/AbstractActionHandlerTest.java2
-rw-r--r--tests/unit/com/android/documentsui/DocumentsAccessTest.java61
-rw-r--r--tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java133
-rw-r--r--tests/unit/com/android/documentsui/PickActivityTest.java108
-rw-r--r--tests/unit/com/android/documentsui/ProfileTabsTest.java23
-rw-r--r--tests/unit/com/android/documentsui/RecentsLoaderTests.java29
-rw-r--r--tests/unit/com/android/documentsui/files/ActionHandlerTest.java2
-rw-r--r--tests/unit/com/android/documentsui/picker/ActionHandlerTest.java101
35 files changed, 778 insertions, 82 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7c719e644..18dc80b0a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -201,6 +201,9 @@
<!-- Toast shown when user want to share files amount over limit [CHAR LIMIT=60] -->
<string name="toast_share_over_limit">Can\u2019t share more than <xliff:g id="count" example="1">%1$d</xliff:g> files</string>
+ <!-- Toast shown when a document is not allowed to share across users. This is a last-resort toast and not expected to be shown. [CHAR LIMIT=48] -->
+ <string name="toast_action_not_allowed">Action not allowed</string>
+
<!-- Title of dialog when prompting user to select an app to share documents with [CHAR LIMIT=32] -->
<string name="share_via">Share via</string>
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 59878ecde..70b946f56 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -89,6 +89,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
@VisibleForTesting
public static final int CODE_FORWARD = 42;
public static final int CODE_AUTHENTICATION = 43;
+ public static final int CODE_FORWARD_CROSS_PROFILE = 44;
@VisibleForTesting
static final int LOADER_ID = 42;
@@ -259,7 +260,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
}
@Override
- public void openRoot(ResolveInfo app) {
+ public void openRoot(ResolveInfo app, UserId userId) {
throw new UnsupportedOperationException("Can't open an app.");
}
@@ -758,6 +759,7 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA
public void loadDocumentsForCurrentStack() {
DocumentStack stack = mState.stack;
if (!stack.isRecents() && stack.isEmpty()) {
+ // TODO: we may also need to reload cross-profile supported root with empty stack
DirectoryResult result = new DirectoryResult();
// TODO (b/35996595): Consider plumbing through the actual exception, though it might
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 189608abb..71cccf9ee 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -93,7 +93,7 @@ public interface ActionHandler {
void openRoot(RootInfo root);
- void openRoot(ResolveInfo app);
+ void openRoot(ResolveInfo app, UserId userId);
void loadRoot(Uri uri, UserId userId);
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index e9f89eaa7..a6850b3fa 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -154,7 +154,7 @@ public abstract class BaseActivity
Metrics.logActivityLaunch(mState, intent);
mProviders = DocumentsApplication.getProvidersCache(this);
- mDocs = DocumentsAccess.create(this);
+ mDocs = DocumentsAccess.create(this, mState);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
diff --git a/src/com/android/documentsui/CrossProfileException.java b/src/com/android/documentsui/CrossProfileException.java
new file mode 100644
index 000000000..cf6e525a1
--- /dev/null
+++ b/src/com/android/documentsui/CrossProfileException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Represents an exception related to cross profile.
+ */
+public abstract class CrossProfileException extends Exception {
+}
diff --git a/src/com/android/documentsui/CrossProfileNoPermissionException.java b/src/com/android/documentsui/CrossProfileNoPermissionException.java
new file mode 100644
index 000000000..484f07e0f
--- /dev/null
+++ b/src/com/android/documentsui/CrossProfileNoPermissionException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Represents an exception when no permission to query the target profile.
+ */
+class CrossProfileNoPermissionException extends CrossProfileException {
+}
diff --git a/src/com/android/documentsui/CrossProfileQuietModeException.java b/src/com/android/documentsui/CrossProfileQuietModeException.java
new file mode 100644
index 000000000..db2df1f08
--- /dev/null
+++ b/src/com/android/documentsui/CrossProfileQuietModeException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Represents an exception when the other profile is in quiet mode.
+ */
+class CrossProfileQuietModeException extends CrossProfileException {
+}
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index cf6953d0b..6de41a909 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -122,27 +122,41 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
ContentProviderClient client = null;
Cursor cursor;
try {
- client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
- if (mDoc.isInArchive()) {
- ArchivesProvider.acquireArchive(client, mUri);
- }
- result.client = client;
-
final Bundle queryArgs = new Bundle();
mModel.addQuerySortArgs(queryArgs);
final List<UserId> userIds = new ArrayList<>();
if (mSearchMode) {
queryArgs.putAll(mQueryArgs);
- if (mState.canShareAcrossProfile && mRoot.supportsCrossProfile()) {
- userIds.addAll(
- DocumentsApplication.getUserIdManager(getContext()).getUserIds());
+ if (mState.supportsCrossProfile() && mRoot.supportsCrossProfile()) {
+ for (UserId userId : DocumentsApplication.getUserIdManager(
+ getContext()).getUserIds()) {
+ if (mState.canInteractWith(userId)) {
+ userIds.add(userId);
+ }
+ }
}
}
if (userIds.isEmpty()) {
userIds.add(mDoc.userId);
}
+ if (userIds.size() == 1) {
+ if (!mState.canInteractWith(mDoc.userId)) {
+ result.exception = new CrossProfileNoPermissionException();
+ return result;
+ } else if (mDoc.userId.isQuietModeEnabled(getContext())) {
+ result.exception = new CrossProfileQuietModeException();
+ return result;
+ }
+ }
+
+ client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
+ if (mDoc.isInArchive()) {
+ ArchivesProvider.acquireArchive(client, mUri);
+ }
+ result.client = client;
+
if (mFeatures.isContentPagingEnabled()) {
// TODO: At some point we don't want forced flags to override real paging...
// and that point is when we have real paging.
@@ -275,7 +289,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
// Ensure the loader is stopped
onStopLoading();
- if (mResult.cursor != null && mObserver != null) {
+ if (mResult != null && mResult.cursor != null && mObserver != null) {
mResult.cursor.unregisterContentObserver(mObserver);
}
diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java
index 9108b15eb..c10f9abef 100644
--- a/src/com/android/documentsui/DocumentsAccess.java
+++ b/src/com/android/documentsui/DocumentsAccess.java
@@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.base.State;
import com.android.documentsui.base.UserId;
import java.io.FileNotFoundException;
@@ -53,15 +54,16 @@ public interface DocumentsAccess {
boolean isDocumentUri(Uri uri);
@Nullable
- Path findDocumentPath(Uri uri, UserId userId) throws RemoteException, FileNotFoundException;
+ Path findDocumentPath(Uri uri, UserId userId)
+ throws RemoteException, FileNotFoundException, CrossProfileNoPermissionException;
List<DocumentInfo> getDocuments(UserId userId, String authority, List<String> docIds)
- throws RemoteException;
+ throws RemoteException, CrossProfileNoPermissionException;
@Nullable Uri createDocument(DocumentInfo parentDoc, String mimeType, String displayName);
- public static DocumentsAccess create(Context context) {
- return new RuntimeDocumentAccess(context);
+ public static DocumentsAccess create(Context context, State state) {
+ return new RuntimeDocumentAccess(context, state);
}
public final class RuntimeDocumentAccess implements DocumentsAccess {
@@ -69,9 +71,11 @@ public interface DocumentsAccess {
private static final String TAG = "DocumentAccess";
private final Context mContext;
+ private final State mState;
- private RuntimeDocumentAccess(Context context) {
+ private RuntimeDocumentAccess(Context context, State state) {
mContext = context;
+ mState = state;
}
@Override
@@ -84,7 +88,9 @@ public interface DocumentsAccess {
@Override
public @Nullable DocumentInfo getDocument(Uri uri, UserId userId) {
try {
- return DocumentInfo.fromUri(userId.getContentResolver(mContext), uri, userId);
+ if (mState.canInteractWith(userId)) {
+ return DocumentInfo.fromUri(userId.getContentResolver(mContext), uri, userId);
+ }
} catch (FileNotFoundException e) {
Log.w(TAG, "Couldn't create DocumentInfo for uri: " + uri);
}
@@ -94,8 +100,10 @@ public interface DocumentsAccess {
@Override
public List<DocumentInfo> getDocuments(UserId userId, String authority, List<String> docIds)
- throws RemoteException {
-
+ throws RemoteException, CrossProfileNoPermissionException {
+ if (!mState.canInteractWith(userId)) {
+ throw new CrossProfileNoPermissionException();
+ }
try (ContentProviderClient client = DocumentsApplication.acquireUnstableProviderOrThrow(
userId.getContentResolver(mContext), authority)) {
@@ -130,7 +138,10 @@ public interface DocumentsAccess {
@Override
public Path findDocumentPath(Uri docUri, UserId userId)
- throws RemoteException, FileNotFoundException {
+ throws RemoteException, FileNotFoundException, CrossProfileNoPermissionException {
+ if (!mState.canInteractWith(userId)) {
+ throw new CrossProfileNoPermissionException();
+ }
final ContentResolver resolver = userId.getContentResolver(mContext);
try (final ContentProviderClient client = DocumentsApplication
.acquireUnstableProviderOrThrow(resolver, docUri.getAuthority())) {
diff --git a/src/com/android/documentsui/Model.java b/src/com/android/documentsui/Model.java
index aef17a380..49e3c9b26 100644
--- a/src/com/android/documentsui/Model.java
+++ b/src/com/android/documentsui/Model.java
@@ -316,6 +316,12 @@ public class Model {
&& mException instanceof AuthenticationRequiredException;
}
+ public boolean hasCrossProfileException() {
+ return mRemoteActionEnabled
+ && hasException()
+ && mException instanceof CrossProfileException;
+ }
+
public @Nullable Exception getException() {
return mException;
}
diff --git a/src/com/android/documentsui/MultiRootDocumentsLoader.java b/src/com/android/documentsui/MultiRootDocumentsLoader.java
index 711a81ca0..e98abbcb1 100644
--- a/src/com/android/documentsui/MultiRootDocumentsLoader.java
+++ b/src/com/android/documentsui/MultiRootDocumentsLoader.java
@@ -247,7 +247,8 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory
HashMap<String, List<RootInfo>> rootsIndex = new HashMap<>();
for (RootInfo root : roots) {
// ignore the root with authority is null. e.g. Recent
- if (root.authority == null || shouldIgnoreRoot(root)) {
+ if (root.authority == null || shouldIgnoreRoot(root)
+ || !mState.canInteractWith(root.userId)) {
continue;
}
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index c8472cf3a..2eb259e40 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -53,6 +53,20 @@ public class RecentsLoader extends MultiRootDocumentsLoader {
}
@Override
+ public DirectoryResult loadInBackground() {
+ if (!mState.canInteractWith(mUserId)) {
+ DirectoryResult result = new DirectoryResult();
+ result.exception = new CrossProfileNoPermissionException();
+ return result;
+ } else if (mUserId.isQuietModeEnabled(getContext())) {
+ DirectoryResult result = new DirectoryResult();
+ result.exception = new CrossProfileQuietModeException();
+ return result;
+ }
+ return super.loadInBackground();
+ }
+
+ @Override
protected long getRejectBeforeTime() {
return System.currentTimeMillis() - REJECT_OLDER_THAN;
}
diff --git a/src/com/android/documentsui/RefreshTask.java b/src/com/android/documentsui/RefreshTask.java
index bcdfac83c..091c64b25 100644
--- a/src/com/android/documentsui/RefreshTask.java
+++ b/src/com/android/documentsui/RefreshTask.java
@@ -85,6 +85,12 @@ public class RefreshTask extends TimeoutTask<Void, Boolean> {
return false;
}
+ if (!mState.canInteractWith(mDoc.userId) || mDoc.userId.isQuietModeEnabled(mContext)) {
+ // No result was returned by these errors so it does not support refresh.
+ Log.w(TAG, "Cannot refresh due to cross profile error.");
+ return false;
+ }
+
// API O introduces ContentResolver#refresh, and if available and the ContentProvider
// 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
@@ -94,7 +100,7 @@ public class RefreshTask extends TimeoutTask<Void, Boolean> {
return false;
}
- final ContentResolver resolver = mContext.getContentResolver();
+ final ContentResolver resolver = mDoc.userId.getContentResolver(mContext);
final String authority = mDoc.authority;
boolean refreshSupported = false;
ContentProviderClient client = null;
diff --git a/src/com/android/documentsui/base/State.java b/src/com/android/documentsui/base/State.java
index 88be98dae..5106b2273 100644
--- a/src/com/android/documentsui/base/State.java
+++ b/src/com/android/documentsui/base/State.java
@@ -90,6 +90,13 @@ public class State implements android.os.Parcelable {
public boolean canShareAcrossProfile = false;
/**
+ * Returns true if we are allowed to interact with the user.
+ */
+ public boolean canInteractWith(UserId userId) {
+ return canShareAcrossProfile || UserId.CURRENT_USER.equals(userId);
+ }
+
+ /**
* This is basically a sub-type for the copy operation. It can be either COPY,
* COMPRESS, EXTRACT or MOVE.
* The only legal values, if set, are: OPERATION_COPY, OPERATION_COMPRESS,
diff --git a/src/com/android/documentsui/base/UserId.java b/src/com/android/documentsui/base/UserId.java
index b65300c32..d17b3e79c 100644
--- a/src/com/android/documentsui/base/UserId.java
+++ b/src/com/android/documentsui/base/UserId.java
@@ -138,6 +138,15 @@ public final class UserId {
}
/**
+ * Returns true if the this user is in quiet mode.
+ */
+ public boolean isQuietModeEnabled(Context context) {
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.isQuietModeEnabled(mUserHandle);
+ }
+
+ /**
* Returns a document uri representing this user.
*/
public Uri buildDocumentUriAsUser(String authority, String documentId) {
diff --git a/src/com/android/documentsui/dirlist/AppsRowItemData.java b/src/com/android/documentsui/dirlist/AppsRowItemData.java
index 305bde20b..21f6af28c 100644
--- a/src/com/android/documentsui/dirlist/AppsRowItemData.java
+++ b/src/com/android/documentsui/dirlist/AppsRowItemData.java
@@ -86,7 +86,7 @@ public abstract class AppsRowItemData {
@Override
protected void onClicked() {
- mActionHandler.openRoot(mResolveInfo);
+ mActionHandler.openRoot(mResolveInfo, mUserId);
}
}
diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java
index a4124f1dc..f07e4e503 100644
--- a/src/com/android/documentsui/dirlist/Message.java
+++ b/src/com/android/documentsui/dirlist/Message.java
@@ -177,8 +177,11 @@ abstract class Message {
@Override
void update(Update event) {
reset();
- if (event.hasException() && !event.hasAuthenticationException()) {
- updateToInflatedErrorMesage();
+ if (event.hasCrossProfileException()) {
+ // TODO: update error message.
+ updateToInflatedErrorMessage();
+ } else if (event.hasException() && !event.hasAuthenticationException()) {
+ updateToInflatedErrorMessage();
} else if (event.hasAuthenticationException()) {
updateToCantDisplayContentMessage();
} else if (mEnv.getModel().getModelIds().length == 0) {
@@ -186,7 +189,7 @@ abstract class Message {
}
}
- private void updateToInflatedErrorMesage() {
+ private void updateToInflatedErrorMessage() {
update(null, mEnv.getContext().getResources().getText(R.string.query_error), null,
mEnv.getContext().getDrawable(R.drawable.hourglass));
}
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index bcbd8e273..03fb04ecc 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -46,6 +46,7 @@ import com.android.documentsui.DocumentsAccess;
import com.android.documentsui.Injector;
import com.android.documentsui.MetricConsts;
import com.android.documentsui.Metrics;
+import com.android.documentsui.UserIdManager;
import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentStack;
@@ -76,18 +77,20 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
private final Features mFeatures;
private final ActivityConfig mConfig;
private final LastAccessedStorage mLastAccessed;
+ private final UserIdManager mUserIdManager;
private UpdatePickResultTask mUpdatePickResultTask;
ActionHandler(
- T activity,
- State state,
- ProvidersAccess providers,
- DocumentsAccess docs,
- SearchViewManager searchMgr,
- Lookup<String, Executor> executors,
- Injector injector,
- LastAccessedStorage lastAccessed) {
+ T activity,
+ State state,
+ ProvidersAccess providers,
+ DocumentsAccess docs,
+ SearchViewManager searchMgr,
+ Lookup<String, Executor> executors,
+ Injector injector,
+ LastAccessedStorage lastAccessed,
+ UserIdManager userIdManager) {
super(activity, state, providers, docs, searchMgr, executors, injector);
mConfig = injector.config;
@@ -95,6 +98,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
mLastAccessed = lastAccessed;
mUpdatePickResultTask = new UpdatePickResultTask(
activity.getApplicationContext(), mInjector.pickResult);
+ mUserIdManager = userIdManager;
}
@Override
@@ -251,15 +255,25 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
// let the user pick another app/backend.
switch (requestCode) {
case CODE_FORWARD:
- onExternalAppResult(resultCode, data);
+ case CODE_FORWARD_CROSS_PROFILE:
+ onExternalAppResult(requestCode, resultCode, data);
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
- private void onExternalAppResult(int resultCode, Intent data) {
+ private void onExternalAppResult(int requestCode, int resultCode, Intent data) {
if (resultCode != FragmentActivity.RESULT_CANCELED) {
+ if (requestCode == CODE_FORWARD_CROSS_PROFILE) {
+ UserId otherUser = UserId.CURRENT_USER.equals(mUserIdManager.getSystemUser())
+ ? mUserIdManager.getManagedUser()
+ : mUserIdManager.getSystemUser();
+ if (!mState.canInteractWith(otherUser)) {
+ mDialogs.showActionNotAllowed();
+ return;
+ }
+ }
// Remember that we last picked via external app
mLastAccessed.setLastAccessedToExternalApp(mActivity);
@@ -287,9 +301,18 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
}
@Override
- public void openRoot(ResolveInfo info) {
+ public void openRoot(ResolveInfo info, UserId userId) {
Metrics.logAppVisited(info);
mInjector.pickResult.increaseActionCount();
+
+ // The App root item should not show if we cannot interact with the target user.
+ // But the user managed to get here, this is the final check of permission. We don't
+ // perform the check on activity result.
+ if (!mState.canInteractWith(userId)) {
+ mInjector.dialogs.showActionNotAllowed();
+ return;
+ }
+
final Intent intent = new Intent(mActivity.getIntent());
final int flagsRemoved = Intent.FLAG_ACTIVITY_FORWARD_RESULT
| Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -300,7 +323,9 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
intent.setComponent(new ComponentName(
info.activityInfo.applicationInfo.packageName, info.activityInfo.name));
try {
- mActivity.startActivityForResult(intent, CODE_FORWARD);
+ boolean isCurrentUser = UserId.CURRENT_USER.equals(userId);
+ mActivity.startActivityForResult(intent,
+ isCurrentUser ? CODE_FORWARD : CODE_FORWARD_CROSS_PROFILE);
} catch (SecurityException | ActivityNotFoundException e) {
Log.e(TAG, "Caught error: " + e.getLocalizedMessage());
mInjector.dialogs.showNoApplicationFound();
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 0e7e47ba9..cdc1a46c6 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -31,6 +31,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.DocumentsContract;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -66,6 +67,7 @@ import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.MessageBuilder;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
public class PickActivity extends BaseActivity implements ActionHandler.Addons {
@@ -134,7 +136,8 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
mSearchManager,
ProviderExecutor::forAuthority,
mInjector,
- LastAccessedStorage.create());
+ LastAccessedStorage.create(),
+ DocumentsApplication.getUserIdManager(this));
mInjector.searchManager = mSearchManager;
@@ -373,6 +376,12 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
mSearchManager.recordHistory();
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
// Explicit file picked, return
+ if (!canShare(Collections.singletonList(doc))) {
+ // A final check to make sure we can share the uri before returning it.
+ Log.e(TAG, "The document cannot be shared");
+ mInjector.dialogs.showActionNotAllowed();
+ return;
+ }
mInjector.actions.finishPicking(doc.getDocumentUri());
mSearchManager.recordHistory();
} else if (mState.action == ACTION_CREATE) {
@@ -384,6 +393,12 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
@Override
public void onDocumentsPicked(List<DocumentInfo> docs) {
if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
+ if (!canShare(docs)) {
+ // A final check to make sure we can share these uris before returning them.
+ Log.e(TAG, "One or more document cannot be shared");
+ mInjector.dialogs.showActionNotAllowed();
+ return;
+ }
final int size = docs.size();
final Uri[] uris = new Uri[size];
for (int i = 0; i < size; i++) {
@@ -394,6 +409,15 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
}
}
+ private boolean canShare(List<DocumentInfo> docs) {
+ for (DocumentInfo doc : docs) {
+ if (!mState.canInteractWith(doc.userId)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@CallSuper
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
diff --git a/src/com/android/documentsui/sidebar/AppItem.java b/src/com/android/documentsui/sidebar/AppItem.java
index 8be1ae5bc..a17ad4f65 100644
--- a/src/com/android/documentsui/sidebar/AppItem.java
+++ b/src/com/android/documentsui/sidebar/AppItem.java
@@ -101,7 +101,7 @@ public class AppItem extends Item {
@Override
void open() {
- mActionHandler.openRoot(info);
+ mActionHandler.openRoot(info, userId);
}
@Override
diff --git a/src/com/android/documentsui/sidebar/RootAndAppItem.java b/src/com/android/documentsui/sidebar/RootAndAppItem.java
index e6b51f911..11d51cb38 100644
--- a/src/com/android/documentsui/sidebar/RootAndAppItem.java
+++ b/src/com/android/documentsui/sidebar/RootAndAppItem.java
@@ -60,7 +60,7 @@ class RootAndAppItem extends RootItem {
@Override
protected void onActionClick(View view) {
- mActionHandler.openRoot(resolveInfo);
+ mActionHandler.openRoot(resolveInfo, userId);
}
@Override
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index d34bceb83..0d83a0f4a 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -401,7 +401,6 @@ public class RootsFragment extends Fragment {
// for change personal profile root.
if (PROFILE_TARGET_ACTIVITY.equals(info.activityInfo.targetActivity)) {
if (UserId.CURRENT_USER.equals(userId)) {
- getBaseActivity().getDisplayState().canShareAcrossProfile = true;
profileItem = new ProfileItem(info, info.loadLabel(pm).toString(),
mActionHandler);
}
@@ -415,6 +414,9 @@ public class RootsFragment extends Fragment {
}
}
+ // TODO: refresh UI
+ getBaseActivity().getDisplayState().canShareAcrossProfile = profileItem != null;
+
// If there are some providers and apps has the same package name, combine them as one item.
for (RootItem rootItem : otherProviders) {
final UserPackage userPackage = new UserPackage(rootItem.userId,
diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java
index 3a74cf9c6..86657fbff 100644
--- a/src/com/android/documentsui/ui/DialogController.java
+++ b/src/com/android/documentsui/ui/DialogController.java
@@ -46,6 +46,7 @@ public interface DialogController {
void showOperationUnsupported();
void showViewInArchivesUnsupported();
void showDocumentsClipped(int size);
+ void showActionNotAllowed();
/**
* Dialogs used when share file count over limit
@@ -136,6 +137,13 @@ public interface DialogController {
}
@Override
+ public void showActionNotAllowed() {
+ // Shows as a last resort when a document is not allowed to share across users
+ Snackbars.makeSnackbar(
+ mActivity, R.string.toast_action_not_allowed, Snackbar.LENGTH_SHORT).show();
+ }
+
+ @Override
public void showNoApplicationFound() {
Snackbars.makeSnackbar(
mActivity, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
diff --git a/tests/common/com/android/documentsui/TestActivity.java b/tests/common/com/android/documentsui/TestActivity.java
index 53f0792df..40de47931 100644
--- a/tests/common/com/android/documentsui/TestActivity.java
+++ b/tests/common/com/android/documentsui/TestActivity.java
@@ -18,6 +18,11 @@ package com.android.documentsui;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
import android.app.ActivityManager;
import android.app.LoaderManager;
import android.content.ComponentName;
@@ -29,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.UserHandle;
+import android.os.UserManager;
import android.test.mock.MockContentResolver;
import android.util.Pair;
@@ -63,6 +69,7 @@ public abstract class TestActivity extends AbstractBase {
public TestLoaderManager loaderManager;
public TestSupportLoaderManager supportLoaderManager;
public ActivityManager activityManager;
+ public UserManager userManager;
public TestEventListener<Intent> startActivity;
public TestEventListener<Pair<Intent, UserHandle>> startActivityAsUser;
@@ -100,6 +107,13 @@ public abstract class TestActivity extends AbstractBase {
loaderManager = new TestLoaderManager();
supportLoaderManager = new TestSupportLoaderManager();
finishedHandler = new TestEventHandler<>();
+
+ // Setup some methods which cannot be overridden.
+ try {
+ doReturn(this).when(this).createPackageContextAsUser(anyString(), anyInt(),
+ any());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
}
@Override
@@ -231,6 +245,8 @@ public abstract class TestActivity extends AbstractBase {
switch (service) {
case Context.ACTIVITY_SERVICE:
return activityManager;
+ case Context.USER_SERVICE:
+ return userManager;
}
throw new IllegalArgumentException("Unknown service " + service);
diff --git a/tests/common/com/android/documentsui/TestUserIdManager.java b/tests/common/com/android/documentsui/TestUserIdManager.java
new file mode 100644
index 000000000..454e13938
--- /dev/null
+++ b/tests/common/com/android/documentsui/TestUserIdManager.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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 com.android.documentsui.base.UserId;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestUserIdManager implements UserIdManager {
+ public List<UserId> userIds = new ArrayList<>();
+ public UserId systemUser = null;
+ public UserId managedUser = null;
+
+ @Override
+ public List<UserId> getUserIds() {
+ return userIds;
+ }
+
+ @Override
+ public UserId getSystemUser() {
+ return systemUser;
+ }
+
+ @Override
+ public UserId getManagedUser() {
+ return managedUser;
+ }
+}
diff --git a/tests/common/com/android/documentsui/testing/UserManagers.java b/tests/common/com/android/documentsui/testing/UserManagers.java
new file mode 100644
index 000000000..5978f8907
--- /dev/null
+++ b/tests/common/com/android/documentsui/testing/UserManagers.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.os.UserManager;
+
+import org.mockito.Mockito;
+
+public class UserManagers {
+
+ private UserManagers() {
+ }
+
+ public static UserManager create() {
+ final UserManager um = Mockito.mock(UserManager.class);
+ when(um.isQuietModeEnabled(any())).thenReturn(false);
+ return um;
+ }
+}
diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java
index 41acee99e..d1b7d0775 100644
--- a/tests/common/com/android/documentsui/ui/TestDialogController.java
+++ b/tests/common/com/android/documentsui/ui/TestDialogController.java
@@ -27,6 +27,7 @@ import junit.framework.Assert;
public class TestDialogController implements DialogController {
private int mFileOpStatus;
+ private boolean mActionNotAllowed;
private boolean mNoApplicationFound;
private boolean mDocumentsClipped;
private boolean mViewInArchivesUnsupported;
@@ -49,6 +50,11 @@ public class TestDialogController implements DialogController {
}
@Override
+ public void showActionNotAllowed() {
+ mActionNotAllowed = true;
+ }
+
+ @Override
public void showNoApplicationFound() {
mNoApplicationFound = true;
}
@@ -87,6 +93,14 @@ public class TestDialogController implements DialogController {
Assert.assertEquals(FileOperations.Callback.STATUS_FAILED, mFileOpStatus);
}
+ public void assertActionNotAllowedShown() {
+ Assert.assertTrue(mActionNotAllowed);
+ }
+
+ public void assertActionNotAllowedNotShown() {
+ Assert.assertFalse(mActionNotAllowed);
+ }
+
public void assertNoAppFoundShown() {
Assert.assertFalse(mNoApplicationFound);
}
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index 3d1e0dbf9..c5c3ac3ca 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -42,6 +42,7 @@ import com.android.documentsui.testing.Roots;
import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestEventHandler;
import com.android.documentsui.testing.TestProvidersAccess;
+import com.android.documentsui.testing.UserManagers;
import org.junit.Before;
import org.junit.Test;
@@ -66,6 +67,7 @@ public class AbstractActionHandlerTest {
public void setUp() {
mEnv = TestEnv.create();
mActivity = TestActivity.create(mEnv);
+ mActivity.userManager = UserManagers.create();
mHandler = new AbstractActionHandler<TestActivity>(
mActivity,
mEnv.state,
diff --git a/tests/unit/com/android/documentsui/DocumentsAccessTest.java b/tests/unit/com/android/documentsui/DocumentsAccessTest.java
new file mode 100644
index 000000000..8a08d431b
--- /dev/null
+++ b/tests/unit/com/android/documentsui/DocumentsAccessTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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 junit.framework.Assert.fail;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.TestProvidersAccess;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class DocumentsAccessTest {
+
+ private TestActivity mActivity;
+ private DocumentsAccess mDocumentsAccess;
+ private TestEnv mEnv;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ mEnv = TestEnv.create();
+ mEnv.reset();
+ mActivity = TestActivity.create(mEnv);
+ mDocumentsAccess = DocumentsAccess.create(mActivity, mEnv.state);
+ }
+
+ @Test
+ public void testCreateDocument_noPermission() throws Exception {
+ try {
+ mDocumentsAccess.getDocuments(TestProvidersAccess.OtherUser.USER_ID, "authority",
+ Lists.newArrayList("docId"));
+ fail("Expects CrossProfileNoPermissionException");
+ } catch (CrossProfileNoPermissionException e) {
+ // expected.
+ }
+ }
+}
diff --git a/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java b/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
index 701d22109..aa084e8d1 100644
--- a/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
+++ b/tests/unit/com/android/documentsui/GlobalSearchLoaderTest.java
@@ -16,6 +16,8 @@
package com.android.documentsui;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@@ -135,6 +137,39 @@ public class GlobalSearchLoaderTest {
}
@Test
+ public void testSearchResult_includeDirectory_excludedOtherUsers() {
+ mEnv.state.canShareAcrossProfile = false;
+
+ TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
+ TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+
+ final DocumentInfo currentUserDoc = mEnv.model.createFile(
+ SEARCH_STRING + "_currentUser.pdf");
+ currentUserDoc.lastModified = System.currentTimeMillis();
+ mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
+ .setNextChildDocumentsReturns(currentUserDoc);
+
+ final DocumentInfo otherUserDoc = mEnv.model.createFile(SEARCH_STRING + "_otherUser.png");
+ otherUserDoc.lastModified = System.currentTimeMillis();
+ mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
+ .setNextChildDocumentsReturns(otherUserDoc);
+
+ final DirectoryResult result = mLoader.loadInBackground();
+ final Cursor c = result.cursor;
+
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ final String docName = c.getString(c.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
+ assertThat(docName).contains("currentUser");
+
+ TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.flags &= ~DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
+ }
+
+ @Test
public void testSearchResult_includeSearchString() {
final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
pdfDoc.lastModified = System.currentTimeMillis();
@@ -193,4 +228,102 @@ public class GlobalSearchLoaderTest {
TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
| DocumentsContract.Root.FLAG_LOCAL_ONLY);
}
+
+ @Test
+ public void testSearchResult_includeCurrentUserRootOnly() {
+ mEnv.state.canShareAcrossProfile = false;
+ mEnv.state.action = State.ACTION_GET_CONTENT;
+
+ final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
+ pdfDoc.lastModified = System.currentTimeMillis();
+
+ final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
+ apkDoc.lastModified = System.currentTimeMillis();
+
+ final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
+ testApkDoc.lastModified = System.currentTimeMillis();
+
+ mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
+ .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
+
+ TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ mEnv.state.sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);
+
+ final DirectoryResult result = mLoader.loadInBackground();
+ final Cursor c = result.cursor;
+
+ assertEquals(1, c.getCount());
+
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ }
+
+
+ @Test
+ public void testSearchResult_includeBothUsersRoots() {
+ mEnv.state.canShareAcrossProfile = true;
+ mEnv.state.action = State.ACTION_GET_CONTENT;
+
+ final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
+ pdfDoc.lastModified = System.currentTimeMillis();
+
+ final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
+ apkDoc.lastModified = System.currentTimeMillis();
+
+ final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
+ testApkDoc.lastModified = System.currentTimeMillis();
+
+ mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
+ .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
+
+ TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ mEnv.state.sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);
+
+ final DirectoryResult result = mLoader.loadInBackground();
+ final Cursor c = result.cursor;
+
+ assertEquals(3, c.getCount());
+
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ }
+
+
+ @Test
+ public void testSearchResult_emptyCurrentUsersRoot() {
+ mEnv.state.canShareAcrossProfile = false;
+ mEnv.state.action = State.ACTION_GET_CONTENT;
+
+ final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
+ pdfDoc.lastModified = System.currentTimeMillis();
+
+ mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
+ .setNextChildDocumentsReturns(pdfDoc);
+
+ TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.OtherUser.USER_ID;
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
+ TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ mEnv.state.sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);
+
+ final DirectoryResult result = mLoader.loadInBackground();
+ assertThat(result.cursor.getCount()).isEqualTo(0);
+ // We don't expect exception even if all roots are from other users.
+ assertThat(result.exception).isNull();
+
+
+ TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
+ TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
+ | DocumentsContract.Root.FLAG_LOCAL_ONLY);
+ }
}
diff --git a/tests/unit/com/android/documentsui/PickActivityTest.java b/tests/unit/com/android/documentsui/PickActivityTest.java
new file mode 100644
index 000000000..d635540cc
--- /dev/null
+++ b/tests/unit/com/android/documentsui/PickActivityTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 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.base.Providers.AUTHORITY_STORAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.DocumentsContract;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.picker.PickActivity;
+import com.android.documentsui.testing.TestProvidersAccess;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+public class PickActivityTest {
+
+ private Intent intentGetContent;
+
+ @Rule
+ public final ActivityTestRule<PickActivity> mRule =
+ new ActivityTestRule<>(PickActivity.class, false, false);
+
+ @Before
+ public void setUp() throws Exception {
+ intentGetContent = new Intent(Intent.ACTION_GET_CONTENT);
+ intentGetContent.addCategory(Intent.CATEGORY_OPENABLE);
+ intentGetContent.setType("*/*");
+ Uri hintUri = DocumentsContract.buildRootUri(AUTHORITY_STORAGE, "primary");
+ intentGetContent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, hintUri);
+ }
+
+ @Test
+ public void testOnDocumentPicked() {
+ DocumentInfo doc = new DocumentInfo();
+ doc.userId = TestProvidersAccess.USER_ID;
+ doc.authority = "authority";
+ doc.documentId = "documentId";
+
+ PickActivity pickActivity = mRule.launchActivity(intentGetContent);
+ pickActivity.mState.canShareAcrossProfile = true;
+ pickActivity.onDocumentPicked(doc);
+ SystemClock.sleep(3000);
+
+ Instrumentation.ActivityResult result = mRule.getActivityResult();
+ assertThat(pickActivity.isFinishing()).isTrue();
+ assertThat(result.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(result.getResultData().getData()).isEqualTo(doc.getDocumentUri());
+ }
+
+ @Test
+ public void testOnDocumentPicked_otherUser() {
+ DocumentInfo doc = new DocumentInfo();
+ doc.userId = TestProvidersAccess.OtherUser.USER_ID;
+ doc.authority = "authority";
+ doc.documentId = "documentId";
+
+ PickActivity pickActivity = mRule.launchActivity(intentGetContent);
+ pickActivity.mState.canShareAcrossProfile = true;
+ pickActivity.onDocumentPicked(doc);
+ SystemClock.sleep(3000);
+
+ Instrumentation.ActivityResult result = mRule.getActivityResult();
+ assertThat(result.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(result.getResultData().getData()).isEqualTo(doc.getDocumentUri());
+ }
+
+ @Test
+ public void testOnDocumentPicked_otherUserDoesNotReturn() {
+ DocumentInfo doc = new DocumentInfo();
+ doc.userId = TestProvidersAccess.OtherUser.USER_ID;
+ doc.authority = "authority";
+ doc.documentId = "documentId";
+
+ PickActivity pickActivity = mRule.launchActivity(intentGetContent);
+ pickActivity.mState.canShareAcrossProfile = false;
+ pickActivity.onDocumentPicked(doc);
+ SystemClock.sleep(3000);
+
+ assertThat(pickActivity.isFinishing()).isFalse();
+ }
+}
diff --git a/tests/unit/com/android/documentsui/ProfileTabsTest.java b/tests/unit/com/android/documentsui/ProfileTabsTest.java
index dc8f0f9cb..5330e9c95 100644
--- a/tests/unit/com/android/documentsui/ProfileTabsTest.java
+++ b/tests/unit/com/android/documentsui/ProfileTabsTest.java
@@ -37,9 +37,7 @@ import com.google.android.material.tabs.TabLayout;
import org.junit.Before;
import org.junit.Test;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
public class ProfileTabsTest {
@@ -233,26 +231,5 @@ public class ProfileTabsTest {
}
}
-
- private static class TestUserIdManager implements UserIdManager {
- List<UserId> userIds = new ArrayList<>();
- UserId systemUser = null;
- UserId managedUser = null;
-
- @Override
- public List<UserId> getUserIds() {
- return userIds;
- }
-
- @Override
- public UserId getSystemUser() {
- return systemUser;
- }
-
- @Override
- public UserId getManagedUser() {
- return managedUser;
- }
- }
}
diff --git a/tests/unit/com/android/documentsui/RecentsLoaderTests.java b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
index 85ab7429f..a1b96ee1f 100644
--- a/tests/unit/com/android/documentsui/RecentsLoaderTests.java
+++ b/tests/unit/com/android/documentsui/RecentsLoaderTests.java
@@ -16,10 +16,15 @@
package com.android.documentsui;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
import android.database.Cursor;
import android.provider.DocumentsContract.Document;
@@ -35,6 +40,7 @@ import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestFileTypeLookup;
import com.android.documentsui.testing.TestImmediateExecutor;
import com.android.documentsui.testing.TestProvidersAccess;
+import com.android.documentsui.testing.UserManagers;
import org.junit.Before;
import org.junit.Test;
@@ -57,9 +63,11 @@ public class RecentsLoaderTests {
mEnv = TestEnv.create();
mActivity = TestActivity.create(mEnv);
mActivity.activityManager = ActivityManagers.create(false);
+ mActivity.userManager = UserManagers.create();
mEnv.state.action = State.ACTION_BROWSE;
mEnv.state.acceptMimes = new String[] { "*/*" };
+ mEnv.state.canShareAcrossProfile = true;
mLoader = new RecentsLoader(mActivity, mEnv.providers, mEnv.state,
TestImmediateExecutor.createLookup(), new TestFileTypeLookup(),
@@ -141,4 +149,25 @@ public class RecentsLoaderTests {
latch.await(1, TimeUnit.SECONDS);
assertTrue(mContentChanged);
}
+
+ @Test
+ public void testLoaderOnUserWithoutPermission() {
+ mEnv.state.canShareAcrossProfile = false;
+ mLoader = new RecentsLoader(mActivity, mEnv.providers, mEnv.state,
+ TestImmediateExecutor.createLookup(), new TestFileTypeLookup(),
+ TestProvidersAccess.OtherUser.USER_ID);
+ final DirectoryResult result = mLoader.loadInBackground();
+
+ assertThat(result.cursor).isNull();
+ assertThat(result.exception).isInstanceOf(CrossProfileNoPermissionException.class);
+ }
+
+ @Test
+ public void testLoaderOnUser_quietMode() {
+ when(mActivity.userManager.isQuietModeEnabled(any())).thenReturn(true);
+ final DirectoryResult result = mLoader.loadInBackground();
+
+ assertThat(result.cursor).isNull();
+ assertThat(result.exception).isInstanceOf(CrossProfileQuietModeException.class);
+ }
}
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 45a0740f2..d7bdbd1ec 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -68,6 +68,7 @@ import com.android.documentsui.testing.TestDragAndDropManager;
import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestFeatures;
import com.android.documentsui.testing.TestProvidersAccess;
+import com.android.documentsui.testing.UserManagers;
import com.android.documentsui.ui.TestDialogController;
import org.junit.Before;
@@ -97,6 +98,7 @@ public class ActionHandlerTest {
mFeatures = new TestFeatures();
mEnv = TestEnv.create(mFeatures);
mActivity = TestActivity.create(mEnv);
+ mActivity.userManager = UserManagers.create();
mActionModeAddons = new TestActionModeAddons();
mDialogs = new TestDialogController();
mClipper = new TestDocumentClipper();
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 57d4df7f5..3098bbb7d 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -16,6 +16,8 @@
package com.android.documentsui.picker;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@ import com.android.documentsui.AbstractActionHandler;
import com.android.documentsui.DocumentsAccess;
import com.android.documentsui.Injector;
import com.android.documentsui.R;
+import com.android.documentsui.TestUserIdManager;
+import com.android.documentsui.UserIdManager;
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.RootInfo;
@@ -69,6 +73,7 @@ public class ActionHandlerTest {
private TestableActionHandler<TestActivity> mHandler;
private TestLastAccessedStorage mLastAccessed;
private PickCountRecordStorage mPickCountRecord;
+ private TestUserIdManager mTestUserIdManager;
@Before
public void setUp() {
@@ -77,6 +82,7 @@ public class ActionHandlerTest {
mEnv.providers.configurePm(mActivity.packageMgr);
mEnv.injector.pickResult = new PickResult();
mLastAccessed = new TestLastAccessedStorage();
+ mTestUserIdManager = new TestUserIdManager();
mPickCountRecord = mock(PickCountRecordStorage.class);
mHandler = new TestableActionHandler<>(
@@ -88,7 +94,8 @@ public class ActionHandlerTest {
mEnv::lookupExecutor,
mEnv.injector,
mLastAccessed,
- mPickCountRecord
+ mPickCountRecord,
+ mTestUserIdManager
);
mEnv.selectionMgr.select("1");
@@ -102,18 +109,20 @@ public class ActionHandlerTest {
private UpdatePickResultTask mTask;
TestableActionHandler(
- T activity,
- State state,
- ProvidersAccess providers,
- DocumentsAccess docs,
- SearchViewManager searchMgr,
- Lookup<String, Executor> executors,
- Injector injector,
- LastAccessedStorage lastAccessed,
- PickCountRecordStorage pickCountRecordStorage) {
- super(activity, state, providers, docs, searchMgr, executors, injector, lastAccessed);
+ T activity,
+ State state,
+ ProvidersAccess providers,
+ DocumentsAccess docs,
+ SearchViewManager searchMgr,
+ Lookup<String, Executor> executors,
+ Injector injector,
+ LastAccessedStorage lastAccessed,
+ PickCountRecordStorage pickCountRecordStorage,
+ UserIdManager userIdManager) {
+ super(activity, state, providers, docs, searchMgr, executors, injector, lastAccessed,
+ userIdManager);
mTask = new UpdatePickResultTask(
- mActivity, mInjector.pickResult, pickCountRecordStorage);
+ mActivity, mInjector.pickResult, pickCountRecordStorage);
}
@Override
@@ -519,6 +528,37 @@ public class ActionHandlerTest {
}
@Test
+ public void testOnAppPickedResult_OnOK_crossProfile() throws Exception {
+ mEnv.state.canShareAcrossProfile = true;
+ mTestUserIdManager.managedUser = TestProvidersAccess.OtherUser.USER_ID;
+ mTestUserIdManager.systemUser = TestProvidersAccess.USER_ID;
+
+ Intent intent = new Intent();
+ mHandler.onActivityResult(AbstractActionHandler.CODE_FORWARD_CROSS_PROFILE,
+ Activity.RESULT_OK, intent);
+ mActivity.finishedHandler.assertCalled();
+ mActivity.setResult.assertCalled();
+
+ assertEquals(Activity.RESULT_OK, (long) mActivity.setResult.getLastValue().first);
+ assertEquals(intent, mActivity.setResult.getLastValue().second);
+ mEnv.dialogs.assertActionNotAllowedNotShown();
+ }
+
+ @Test
+ public void testOnAppPickedResult_OnOK_crossProfile_withoutPermission() throws Exception {
+ mEnv.state.canShareAcrossProfile = false;
+ mTestUserIdManager.managedUser = TestProvidersAccess.OtherUser.USER_ID;
+ mTestUserIdManager.systemUser = TestProvidersAccess.USER_ID;
+
+ Intent intent = new Intent();
+ mHandler.onActivityResult(AbstractActionHandler.CODE_FORWARD_CROSS_PROFILE,
+ Activity.RESULT_OK, intent);
+ mActivity.finishedHandler.assertNotCalled();
+ mActivity.setResult.assertNotCalled();
+ mEnv.dialogs.assertActionNotAllowedShown();
+ }
+
+ @Test
public void testOnAppPickedResult_OnNotOK() throws Exception {
Intent intent = new Intent();
mHandler.onActivityResult(0, Activity.RESULT_OK, intent);
@@ -532,8 +572,18 @@ public class ActionHandlerTest {
}
@Test
+ public void testOnAppPickedResult_OnNotOK_crossProfile() throws Exception {
+ Intent intent = new Intent();
+ mHandler.onActivityResult(AbstractActionHandler.CODE_FORWARD_CROSS_PROFILE,
+ Activity.RESULT_CANCELED,
+ intent);
+ mActivity.finishedHandler.assertNotCalled();
+ mActivity.setResult.assertNotCalled();
+ }
+
+ @Test
public void testOpenAppRoot() throws Exception {
- mHandler.openRoot(TestResolveInfo.create());
+ mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.USER_ID);
assertEquals((long) mActivity.startActivityForResult.getLastValue().second,
AbstractActionHandler.CODE_FORWARD);
assertNotNull(mActivity.startActivityForResult.getLastValue().first);
@@ -546,7 +596,7 @@ public class ActionHandlerTest {
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
- mHandler.openRoot(TestResolveInfo.create());
+ mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.USER_ID);
assertEquals((long) mActivity.startActivityForResult.getLastValue().second,
AbstractActionHandler.CODE_FORWARD);
assertNotNull(mActivity.startActivityForResult.getLastValue().first);
@@ -563,10 +613,31 @@ public class ActionHandlerTest {
public void testOpenAppRootWithQueryContent_matchedContent() throws Exception {
final String queryContent = "query";
mActivity.intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryContent);
- mHandler.openRoot(TestResolveInfo.create());
+ mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.USER_ID);
+ assertEquals(queryContent,
+ mActivity.startActivityForResult.getLastValue().first.getStringExtra(
+ Intent.EXTRA_CONTENT_QUERY));
+ }
+
+ @Test
+ public void testOpenAppRoot_doesNotHappen_differentUser() throws Exception {
+ final String queryContent = "query";
+ mActivity.intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryContent);
+ mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.OtherUser.USER_ID);
+ assertThat(mActivity.startActivityForResult.getLastValue()).isNull();
+ mEnv.dialogs.assertActionNotAllowedShown();
+ }
+
+ @Test
+ public void testOpenAppRoot_happenWithPermission_differentUser() throws Exception {
+ final String queryContent = "query";
+ mEnv.state.canShareAcrossProfile = true;
+ mActivity.intent.putExtra(Intent.EXTRA_CONTENT_QUERY, queryContent);
+ mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.OtherUser.USER_ID);
assertEquals(queryContent,
mActivity.startActivityForResult.getLastValue().first.getStringExtra(
Intent.EXTRA_CONTENT_QUERY));
+ mEnv.dialogs.assertActionNotAllowedNotShown();
}
@Test