summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kelvin Kwan <kelvinkwan@google.com> 2020-03-02 16:52:59 +0000
committer Kelvin Kwan <kelvinkwan@google.com> 2020-03-10 09:55:01 +0000
commit34810bc0cf5a8a927c80571d226bf1feed6ba911 (patch)
treed31f6767fdf6e60c3aba86e778fe8de4f84fa06d
parentb7b46985a6672fa827b30fd66ce0089de598488d (diff)
Add error handling and UI on WP quiet mode / no permission to share
Listen to WP on/off When broadcast is received, update roots and then refresh the dir if needed. If canShareAcrossProfile value changes in RootsFragment, refresh the directory. Add 2 new inflated message error screens When WP is in quiet mode, show a button to turn on WP MODIFY_QUIET_MODE(privileged) is required. Bug: 148270816 Test: atest DocumentsUIGoogleTests Test: manual - turning on/off WP > message shown/disappear Test: manual - changing dpc policy > message shown/disappear Change-Id: Icd307e503294aae4a8c7a9c3091facee7f6ec814
-rw-r--r--AndroidManifest.xml1
-rw-r--r--res/drawable/share_off.xml26
-rw-r--r--res/drawable/work_off.xml26
-rw-r--r--res/layout/item_doc_inflated_message.xml31
-rw-r--r--res/layout/item_doc_inflated_message_content.xml45
-rw-r--r--res/layout/item_doc_inflated_message_cross_profile.xml50
-rw-r--r--res/values/colors.xml3
-rw-r--r--res/values/strings.xml18
-rw-r--r--res/values/styles.xml4
-rw-r--r--res/values/styles_text.xml17
-rw-r--r--src/com/android/documentsui/CrossProfileNoPermissionException.java2
-rw-r--r--src/com/android/documentsui/CrossProfileQuietModeException.java13
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java2
-rw-r--r--src/com/android/documentsui/DocumentsApplication.java45
-rw-r--r--src/com/android/documentsui/RecentsLoader.java2
-rw-r--r--src/com/android/documentsui/UserIdManager.java4
-rw-r--r--src/com/android/documentsui/base/UserId.java13
-rw-r--r--src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java65
-rw-r--r--src/com/android/documentsui/dirlist/Message.java60
-rw-r--r--src/com/android/documentsui/roots/ProvidersCache.java23
-rw-r--r--src/com/android/documentsui/sidebar/RootsFragment.java13
-rw-r--r--tests/common/com/android/documentsui/dirlist/TestEnvironment.java96
-rw-r--r--tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java67
-rw-r--r--tests/unit/com/android/documentsui/dirlist/MessageTest.java109
-rw-r--r--tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java66
25 files changed, 612 insertions, 189 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c95847546..151af406f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
<!-- Permissions required for reading and logging compat changes -->
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
diff --git a/res/drawable/share_off.xml b/res/drawable/share_off.xml
new file mode 100644
index 000000000..23e5c564f
--- /dev/null
+++ b/res/drawable/share_off.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.7225,20.9245L21.2011,22.4031L22.4032,21.201L2.8022,1.6L1.6001,2.8021L8.1265,9.3284L7.64,9.612C7.1,9.112 6.39,8.802 5.6,8.802C3.94,8.802 2.6,10.142 2.6,11.802C2.6,13.462 3.94,14.802 5.6,14.802C6.39,14.802 7.1,14.492 7.64,13.992L14.69,18.112C14.64,18.332 14.6,18.562 14.6,18.802C14.6,20.462 15.94,21.802 17.6,21.802C18.43,21.802 19.18,21.467 19.7225,20.9245ZM16.8938,18.0958L18.3063,19.5083C18.125,19.6895 17.875,19.802 17.6,19.802C17.05,19.802 16.6,19.352 16.6,18.802C16.6,18.527 16.7125,18.277 16.8938,18.0958ZM15.1871,16.3891L9.3881,10.5901L8.51,11.102C8.56,11.332 8.6,11.562 8.6,11.802C8.6,12.042 8.56,12.272 8.51,12.502L15.1871,16.3891ZM15.56,6.992L12.4382,8.8119L11.1766,7.5503L14.69,5.502C14.64,5.282 14.6,5.042 14.6,4.802C14.6,3.142 15.94,1.802 17.6,1.802C19.26,1.802 20.6,3.142 20.6,4.802C20.6,6.462 19.26,7.802 17.6,7.802C16.81,7.802 16.09,7.492 15.56,6.992ZM18.6,4.802C18.6,4.252 18.15,3.802 17.6,3.802C17.05,3.802 16.6,4.252 16.6,4.802C16.6,5.352 17.05,5.802 17.6,5.802C18.15,5.802 18.6,5.352 18.6,4.802ZM5.6,12.802C5.05,12.802 4.6,12.352 4.6,11.802C4.6,11.252 5.05,10.802 5.6,10.802C6.15,10.802 6.6,11.252 6.6,11.802C6.6,12.352 6.15,12.802 5.6,12.802Z"
+ android:fillType="evenOdd"
+ android:fillColor="@color/error_image_color"/>
+</vector>
diff --git a/res/drawable/work_off.xml b/res/drawable/work_off.xml
new file mode 100644
index 000000000..323f5e884
--- /dev/null
+++ b/res/drawable/work_off.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/error_image_color"
+ android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v1.17L10.83,8L20,8v9.17l1.98,1.98c0,-0.05 0.02,-0.1 0.02,-0.16L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2zM19,19L8,8 6,6 2.81,2.81 1.39,4.22 3.3,6.13C2.54,6.41 2.01,7.14 2.01,8L2,19c0,1.11 0.89,2 2,2h14.17l1.61,1.61 1.41,-1.41 -0.37,-0.37L19,19zM4,19L4,8h1.17l11,11L4,19z"/>
+</vector>
+
diff --git a/res/layout/item_doc_inflated_message.xml b/res/layout/item_doc_inflated_message.xml
index c4e95f4c9..abfdbfd10 100644
--- a/res/layout/item_doc_inflated_message.xml
+++ b/res/layout/item_doc_inflated_message.xml
@@ -22,30 +22,7 @@
android:background="?android:attr/colorBackground"
android:focusable="true">
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <ImageView
- android:id="@+id/artwork"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="25dp"
- android:layout_marginBottom="25dp"
- android:scaleType="fitCenter"
- android:maxHeight="250dp"
- android:adjustViewBounds="true"
- android:contentDescription="@null"/>
-
- <TextView
- android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="25dp"
- android:gravity="center_horizontal"
- style="?android:attr/textAppearanceListItem"/>
-
- </LinearLayout>
-</FrameLayout> \ No newline at end of file
+ <include android:id="@+id/content" layout="@layout/item_doc_inflated_message_content"/>
+ <include android:id="@+id/cross_profile"
+ layout="@layout/item_doc_inflated_message_cross_profile"/>
+</FrameLayout>
diff --git a/res/layout/item_doc_inflated_message_content.xml b/res/layout/item_doc_inflated_message_content.xml
new file mode 100644
index 000000000..0635e1654
--- /dev/null
+++ b/res/layout/item_doc_inflated_message_content.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/artwork"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:layout_marginBottom="25dp"
+ android:scaleType="fitCenter"
+ android:maxHeight="250dp"
+ android:adjustViewBounds="true"
+ android:gravity="bottom|center_horizontal"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="25dp"
+ android:gravity="center_horizontal"
+ style="?android:attr/textAppearanceListItem"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/item_doc_inflated_message_cross_profile.xml b/res/layout/item_doc_inflated_message_cross_profile.xml
new file mode 100644
index 000000000..301467993
--- /dev/null
+++ b/res/layout/item_doc_inflated_message_cross_profile.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="424dp"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp">
+
+ <ImageView
+ android:id="@+id/artwork"
+ android:layout_width="32dp"
+ android:layout_height="32dp"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_marginTop="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/EmptyStateTitleText"/>
+ <TextView
+ android:id="@+id/message"
+ android:layout_marginTop="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textAppearance="@style/EmptyStateMessageText"/>
+ <Button
+ android:id="@+id/button"
+ android:layout_marginTop="24dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/EmptyStateButton"/>
+</LinearLayout> \ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index af5201a93..fb00e168c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -43,4 +43,7 @@
<color name="shortcut_background">#fff5f5f5</color>
<color name="briefcase_icon_color">#1A73E8</color>
+ <color name="cross_profile_button_text_color">#1A73E8</color>
+ <color name="empty_state_text">#202124</color>
+ <color name="error_image_color">#757575</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 18dc80b0a..30a0c78b2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -173,6 +173,24 @@
<!-- Error message shown when querying for a list of documents failed [CHAR LIMIT=48] -->
<string name="query_error">Can\u2019t load content at the moment</string>
+ <!-- Error message title shown when the target profile is in quiet mode [CHAR LIMIT=72] -->
+ <string name="quiet_mode_error_title">Turn on work apps</string>
+ <!-- Error message content shown when the target profile is in quiet mode [CHAR LIMIT=72] -->
+ <string name="quiet_mode_error_message">Turn on work apps to access work files</string>
+ <!-- Button text shown on a button when work profile is off. Clicking on the button will switch on the work profile [CHAR LIMIT=48] -->
+ <string name="quiet_mode_button">Switch on work</string>
+
+ <!-- Error message title shown when the admin does not allow the user to share files across profile [CHAR LIMIT=72] -->
+ <string name="cant_share_across_profile_error_title">Can\u2019t share across profiles</string>
+ <!-- Error message content shown when the admin does not allow the user to share files across profile. Shows in work tab[CHAR LIMIT=200] -->
+ <string name="cant_share_to_personal_error_message">Your IT admin does not allow you to access
+ work files from a personal app
+ </string>
+ <!-- Error message content shown when the admin does not allow the user to share files across profile. Shows in personal tab[CHAR LIMIT=200] -->
+ <string name="cant_share_to_work_error_message">Your IT admin does not allow you to
+ access personal files from a work app
+ </string>
+
<!-- Title of storage root location that contains recently modified or used documents [CHAR LIMIT=24] -->
<string name="root_recent">Recent</string>
<!-- Subtitle of storage root indicating the total free space available, in bytes [CHAR LIMIT=24] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8c3fd06f0..a86262d18 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -87,6 +87,10 @@
<item name="android:textAppearance">@style/MaterialButtonTextAppearance</item>
</style>
+ <style name="EmptyStateButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
+ <item name="android:textAppearance">@style/EmptyStateButtonTextAppearance</item>
+ </style>
+
<style name="AlertDialogTheme" parent="@style/ThemeOverlay.AppCompat.Dialog.Alert">
<item name="buttonBarPositiveButtonStyle">@style/DialogTextButton</item>
<item name="buttonBarNegativeButtonStyle">@style/DialogTextButton</item>
diff --git a/res/values/styles_text.xml b/res/values/styles_text.xml
index 689db4941..84fefe8f9 100644
--- a/res/values/styles_text.xml
+++ b/res/values/styles_text.xml
@@ -90,4 +90,21 @@
<item name="fontFamily">@string/config_fontFamilyMedium</item>
</style>
+ <style name="EmptyStateTitleText">
+ <item name="android:textColor">@color/empty_state_text</item>
+ <item name="android:textSize">18sp</item>
+ <item name="fontFamily">@string/config_fontFamilyMedium</item>
+ </style>
+
+ <style name="EmptyStateMessageText">
+ <item name="android:textColor">@color/empty_state_text</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+
+ <style name="EmptyStateButtonTextAppearance">
+ <item name="android:textColor">@color/cross_profile_button_text_color</item>
+ <item name="android:textSize">14sp</item>
+ <item name="fontFamily">@string/config_fontFamilyMedium</item>
+ </style>
+
</resources> \ No newline at end of file
diff --git a/src/com/android/documentsui/CrossProfileNoPermissionException.java b/src/com/android/documentsui/CrossProfileNoPermissionException.java
index 484f07e0f..d945dc728 100644
--- a/src/com/android/documentsui/CrossProfileNoPermissionException.java
+++ b/src/com/android/documentsui/CrossProfileNoPermissionException.java
@@ -19,5 +19,5 @@ package com.android.documentsui;
/**
* Represents an exception when no permission to query the target profile.
*/
-class CrossProfileNoPermissionException extends CrossProfileException {
+public class CrossProfileNoPermissionException extends CrossProfileException {
}
diff --git a/src/com/android/documentsui/CrossProfileQuietModeException.java b/src/com/android/documentsui/CrossProfileQuietModeException.java
index db2df1f08..6d7c168a2 100644
--- a/src/com/android/documentsui/CrossProfileQuietModeException.java
+++ b/src/com/android/documentsui/CrossProfileQuietModeException.java
@@ -16,8 +16,17 @@
package com.android.documentsui;
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import com.android.documentsui.base.UserId;
+
/**
- * Represents an exception when the other profile is in quiet mode.
+ * Represents an exception when the given profile is in quiet mode.
*/
-class CrossProfileQuietModeException extends CrossProfileException {
+public class CrossProfileQuietModeException extends CrossProfileException {
+ public final UserId mUserId;
+
+ public CrossProfileQuietModeException(UserId userId) {
+ mUserId = checkNotNull(userId);
+ }
}
diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java
index 9a9cba48e..87753a831 100644
--- a/src/com/android/documentsui/DirectoryLoader.java
+++ b/src/com/android/documentsui/DirectoryLoader.java
@@ -146,7 +146,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
result.exception = new CrossProfileNoPermissionException();
return result;
} else if (mRoot.userId.isQuietModeEnabled(getContext())) {
- result.exception = new CrossProfileQuietModeException();
+ result.exception = new CrossProfileQuietModeException(mRoot.userId);
return result;
} else if (mDoc == null) {
// TODO (b/35996595): Consider plumbing through the actual exception, though it
diff --git a/src/com/android/documentsui/DocumentsApplication.java b/src/com/android/documentsui/DocumentsApplication.java
index 745da0afd..c7e21a434 100644
--- a/src/com/android/documentsui/DocumentsApplication.java
+++ b/src/com/android/documentsui/DocumentsApplication.java
@@ -30,6 +30,8 @@ import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
import com.android.documentsui.base.Lookup;
import com.android.documentsui.base.UserId;
import com.android.documentsui.clipping.ClipStorage;
@@ -39,10 +41,28 @@ import com.android.documentsui.queries.SearchHistoryManager;
import com.android.documentsui.roots.ProvidersCache;
import com.android.documentsui.theme.ThemeOverlayManager;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
public class DocumentsApplication extends Application {
private static final String TAG = "DocumentsApplication";
private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
+ private static final List<String> PACKAGE_FILTER_ACTIONS = Lists.newArrayList(
+ Intent.ACTION_PACKAGE_ADDED,
+ Intent.ACTION_PACKAGE_CHANGED,
+ Intent.ACTION_PACKAGE_REMOVED,
+ Intent.ACTION_PACKAGE_DATA_CLEARED
+ );
+
+ private static final List<String> MANAGED_PROFILE_FILTER_ACTIONS = Lists.newArrayList(
+ Intent.ACTION_MANAGED_PROFILE_ADDED,
+ Intent.ACTION_MANAGED_PROFILE_REMOVED,
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
+ );
+
private ProvidersCache mProviders;
private ThumbnailCache mThumbnailCache;
private ClipStorage mClipStore;
@@ -113,7 +133,7 @@ public class DocumentsApplication extends Application {
mUserIdManager = UserIdManager.create(this);
mProviders = new ProvidersCache(this, mUserIdManager);
- mProviders.updateAsync(false);
+ mProviders.updateAsync(/* forceRefreshAll= */ false, /* callback= */ null);
mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4);
@@ -127,10 +147,9 @@ public class DocumentsApplication extends Application {
mFileTypeLookup = new FileTypeMap(this);
final IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ for (String packageAction : PACKAGE_FILTER_ACTIONS) {
+ packageFilter.addAction(packageAction);
+ }
packageFilter.addDataScheme("package");
registerReceiver(mCacheReceiver, packageFilter);
@@ -138,6 +157,12 @@ public class DocumentsApplication extends Application {
localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
registerReceiver(mCacheReceiver, localeFilter);
+ final IntentFilter managedProfileFilter = new IntentFilter();
+ for (String managedProfileAction : MANAGED_PROFILE_FILTER_ACTIONS) {
+ managedProfileFilter.addAction(managedProfileAction);
+ }
+ registerReceiver(mCacheReceiver, managedProfileFilter);
+
SearchHistoryManager.getInstance(getApplicationContext());
}
@@ -152,11 +177,17 @@ public class DocumentsApplication extends Application {
@Override
public void onReceive(Context context, Intent intent) {
final Uri data = intent.getData();
- if (data != null) {
+ final String action = intent.getAction();
+ if (PACKAGE_FILTER_ACTIONS.contains(action) && data != null) {
final String packageName = data.getSchemeSpecificPart();
mProviders.updatePackageAsync(UserId.DEFAULT_USER, packageName);
+ } else if (MANAGED_PROFILE_FILTER_ACTIONS.contains(action)) {
+ // After we have reloaded roots. Resend the broadcast locally so the other
+ // components can reload properly after roots are updated.
+ mProviders.updateAsync(/* forceRefreshAll= */ true,
+ () -> LocalBroadcastManager.getInstance(context).sendBroadcast(intent));
} else {
- mProviders.updateAsync(true);
+ mProviders.updateAsync(/* forceRefreshAll= */ true, /* callback= */ null);
}
}
};
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index 2eb259e40..62ea6d05f 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -60,7 +60,7 @@ public class RecentsLoader extends MultiRootDocumentsLoader {
return result;
} else if (mUserId.isQuietModeEnabled(getContext())) {
DirectoryResult result = new DirectoryResult();
- result.exception = new CrossProfileQuietModeException();
+ result.exception = new CrossProfileQuietModeException(mUserId);
return result;
}
return super.loadInBackground();
diff --git a/src/com/android/documentsui/UserIdManager.java b/src/com/android/documentsui/UserIdManager.java
index 3458fd973..b3cc8d0c3 100644
--- a/src/com/android/documentsui/UserIdManager.java
+++ b/src/com/android/documentsui/UserIdManager.java
@@ -114,8 +114,8 @@ public interface UserIdManager {
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_REMOVED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
mContext.registerReceiver(mIntentReceiver, filter);
}
diff --git a/src/com/android/documentsui/base/UserId.java b/src/com/android/documentsui/base/UserId.java
index d17b3e79c..7203a9593 100644
--- a/src/com/android/documentsui/base/UserId.java
+++ b/src/com/android/documentsui/base/UserId.java
@@ -147,6 +147,19 @@ public final class UserId {
}
/**
+ * Disables quiet mode for a managed profile. The caller should check {@code
+ * MODIFY_QUIET_MODE} permission first.
+ *
+ * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+ * {@code true} otherwise
+ */
+ public boolean requestQuietModeDisabled(Context context) {
+ final UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.requestQuietModeEnabled(false, mUserHandle);
+ }
+
+ /**
* Returns a document uri representing this user.
*/
public Uri buildDocumentUriAsUser(String authority, String documentId) {
diff --git a/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java b/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java
index e332feefd..f655d69b0 100644
--- a/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java
@@ -18,6 +18,8 @@ package com.android.documentsui.dirlist;
import android.content.Context;
import android.database.Cursor;
+import android.text.TextUtils;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -30,14 +32,34 @@ import com.android.documentsui.R;
* Used by {@link DirectoryAddonsAdapter}.
*/
final class InflateMessageDocumentHolder extends MessageHolder {
+ public static final int LAYOUT_CROSS_PROFILE_ERROR = 1;
+
private Message mMessage;
- private TextView mMsgView;
- private ImageView mImageView;
+
+ private TextView mContentMessage;
+ private ImageView mContentImage;
+
+ private TextView mCrossProfileTitle;
+ private TextView mCrossProfileMessage;
+ private ImageView mCrossProfileImage;
+ private TextView mCrossProfileButton;
+
+
+ private View mContentView;
+ private View mCrossProfileView;
public InflateMessageDocumentHolder(Context context, ViewGroup parent) {
super(context, parent, R.layout.item_doc_inflated_message);
- mMsgView = (TextView) itemView.findViewById(R.id.message);
- mImageView = (ImageView) itemView.findViewById(R.id.artwork);
+ mContentView = itemView.findViewById(R.id.content);
+ mCrossProfileView = itemView.findViewById(R.id.cross_profile);
+
+ mContentMessage = mContentView.findViewById(R.id.message);
+ mContentImage = mContentView.findViewById(R.id.artwork);
+
+ mCrossProfileTitle = mCrossProfileView.findViewById(R.id.title);
+ mCrossProfileMessage = mCrossProfileView.findViewById(R.id.message);
+ mCrossProfileImage = mCrossProfileView.findViewById(R.id.artwork);
+ mCrossProfileButton = mCrossProfileView.findViewById(R.id.button);
}
public void bind(Message message) {
@@ -47,7 +69,38 @@ final class InflateMessageDocumentHolder extends MessageHolder {
@Override
public void bind(Cursor cursor, String modelId) {
- mMsgView.setText(mMessage.getMessageString());
- mImageView.setImageDrawable(mMessage.getIcon());
+ if (mMessage.getLayout() == LAYOUT_CROSS_PROFILE_ERROR) {
+ bindCrossProfileMessageView();
+ } else {
+ bindContentMessageView();
+ }
+ }
+
+ private void onButtonClick(View button) {
+ mMessage.runCallback();
+ }
+
+ private void bindContentMessageView() {
+ mContentView.setVisibility(View.VISIBLE);
+ mCrossProfileView.setVisibility(View.GONE);
+
+ mContentMessage.setText(mMessage.getMessageString());
+ mContentImage.setImageDrawable(mMessage.getIcon());
+ }
+
+ private void bindCrossProfileMessageView() {
+ mContentView.setVisibility(View.GONE);
+ mCrossProfileView.setVisibility(View.VISIBLE);
+
+ mCrossProfileTitle.setText(mMessage.getTitleString());
+ mCrossProfileMessage.setText(mMessage.getMessageString());
+ mCrossProfileImage.setImageDrawable(mMessage.getIcon());
+ if (!TextUtils.isEmpty(mMessage.getButtonString())) {
+ mCrossProfileButton.setVisibility(View.VISIBLE);
+ mCrossProfileButton.setText(mMessage.getButtonString());
+ mCrossProfileButton.setOnClickListener(this::onButtonClick);
+ } else {
+ mCrossProfileButton.setVisibility(View.GONE);
+ }
}
}
diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java
index ebbe867a7..93eef0477 100644
--- a/src/com/android/documentsui/dirlist/Message.java
+++ b/src/com/android/documentsui/dirlist/Message.java
@@ -16,16 +16,22 @@
package com.android.documentsui.dirlist;
+import android.Manifest;
import android.app.AuthenticationRequiredException;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
import androidx.annotation.Nullable;
+import com.android.documentsui.CrossProfileNoPermissionException;
+import com.android.documentsui.CrossProfileQuietModeException;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Model.Update;
import com.android.documentsui.R;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
+import com.android.documentsui.base.UserId;
import com.android.documentsui.dirlist.DocumentsAdapter.Environment;
/**
@@ -45,6 +51,7 @@ abstract class Message {
private @Nullable Drawable mIcon;
private boolean mShouldShow = false;
protected boolean mShouldKeep = false;
+ protected int mLayout;
Message(Environment env, Runnable defaultCallback) {
mEnv = env;
@@ -69,6 +76,7 @@ abstract class Message {
mMessageString = null;
mIcon = null;
mShouldShow = false;
+ mLayout = 0;
}
void runCallback() {
@@ -83,6 +91,10 @@ abstract class Message {
return mIcon;
}
+ int getLayout() {
+ return mLayout;
+ }
+
boolean shouldShow() {
return mShouldShow;
}
@@ -171,16 +183,27 @@ abstract class Message {
final static class InflateMessage extends Message {
+ private final boolean mCanModifyQuietMode;
+
InflateMessage(Environment env, Runnable callback) {
super(env, callback);
+ mCanModifyQuietMode =
+ mEnv.getContext().checkSelfPermission(Manifest.permission.MODIFY_QUIET_MODE)
+ == PackageManager.PERMISSION_GRANTED;
}
@Override
void update(Update event) {
reset();
if (event.hasCrossProfileException()) {
- // TODO: update error message.
- updateToInflatedErrorMessage();
+ if (event.getException() instanceof CrossProfileQuietModeException) {
+ updateToQuietModeErrorMessage(
+ ((CrossProfileQuietModeException) event.getException()).mUserId);
+ } else if (event.getException() instanceof CrossProfileNoPermissionException) {
+ updateToCrossProfileNoPermissionErrorMessage();
+ } else {
+ updateToInflatedErrorMessage();
+ }
} else if (event.hasException() && !event.hasAuthenticationException()) {
updateToInflatedErrorMessage();
} else if (event.hasAuthenticationException()) {
@@ -190,6 +213,39 @@ abstract class Message {
}
}
+ private void updateToQuietModeErrorMessage(UserId userId) {
+ mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
+ CharSequence buttonText = null;
+ if (mCanModifyQuietMode) {
+ buttonText = mEnv.getContext().getResources().getText(R.string.quiet_mode_button);
+ mCallback = () ->
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ userId.requestQuietModeDisabled(mEnv.getContext());
+ return null;
+ }
+ }.execute();
+ }
+ update(
+ mEnv.getContext().getResources().getText(R.string.quiet_mode_error_title),
+ mEnv.getContext().getResources().getText(R.string.quiet_mode_error_message),
+ buttonText,
+ mEnv.getContext().getDrawable(R.drawable.work_off));
+ }
+
+ private void updateToCrossProfileNoPermissionErrorMessage() {
+ mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
+ update(
+ mEnv.getContext().getResources().getText(
+ R.string.cant_share_across_profile_error_title),
+ mEnv.getContext().getResources().getText(UserId.CURRENT_USER.isSystem()
+ ? R.string.cant_share_to_personal_error_message
+ : R.string.cant_share_to_work_error_message),
+ /* buttonString= */ null,
+ mEnv.getContext().getDrawable(R.drawable.share_off));
+ }
+
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/roots/ProvidersCache.java b/src/com/android/documentsui/roots/ProvidersCache.java
index e9ebe91f9..f35d05e2f 100644
--- a/src/com/android/documentsui/roots/ProvidersCache.java
+++ b/src/com/android/documentsui/roots/ProvidersCache.java
@@ -187,7 +187,7 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName {
return mObservedAuthoritiesDetails.get(new UserAuthority(userId, authority)).packageName;
}
- public void updateAsync(boolean forceRefreshAll) {
+ public void updateAsync(boolean forceRefreshAll, @Nullable Runnable callback) {
// NOTE: This method is called when the UI language changes.
// For that reason we update our RecentsRoot to reflect
@@ -205,12 +205,13 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName {
assert (recentRoot.availableBytes == -1);
}
- new UpdateTask(forceRefreshAll, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ new UpdateTask(forceRefreshAll, null, callback).executeOnExecutor(
+ AsyncTask.THREAD_POOL_EXECUTOR);
}
public void updatePackageAsync(UserId userId, String packageName) {
- new UpdateTask(false, new UserPackage(userId, packageName)).executeOnExecutor(
- AsyncTask.THREAD_POOL_EXECUTOR);
+ new UpdateTask(false, new UserPackage(userId, packageName),
+ /* callback= */ null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public void updateAuthorityAsync(UserId userId, String authority) {
@@ -488,6 +489,8 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName {
private final boolean mForceRefreshAll;
@Nullable
private final UserPackage mForceRefreshUserPackage;
+ @Nullable
+ private final Runnable mCallback;
private final Multimap<UserAuthority, RootInfo> mTaskRoots = ArrayListMultimap.create();
private final HashSet<UserAuthority> mTaskStoppedAuthorities = new HashSet<>();
@@ -499,10 +502,13 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName {
* all packages should be ignored.
* @param forceRefreshUserPackage when non-null, all previously cached
* values for this specific user package should be ignored.
+ * @param callback when non-null, it will be invoked after the task is executed.
*/
- UpdateTask(boolean forceRefreshAll, @Nullable UserPackage forceRefreshUserPackage) {
+ UpdateTask(boolean forceRefreshAll, @Nullable UserPackage forceRefreshUserPackage,
+ @Nullable Runnable callback) {
mForceRefreshAll = forceRefreshAll;
mForceRefreshUserPackage = forceRefreshUserPackage;
+ mCallback = callback;
}
@Override
@@ -542,6 +548,13 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName {
return null;
}
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ if (mCallback != null) {
+ mCallback.run();
+ }
+ }
+
private void handleDocumentsProvider(ProviderInfo info, UserId userId) {
UserAuthority userAuthority = new UserAuthority(userId, info.authority);
// Ignore stopped packages for now; we might query them
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index db3171d3c..ed9ba1294 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -413,8 +413,13 @@ public class RootsFragment extends Fragment {
}
}
- // TODO: refresh UI
- getBaseActivity().getDisplayState().canShareAcrossProfile = profileItem != null;
+ boolean canShareAcrossProfile = profileItem != null;
+ if (getBaseActivity().getDisplayState().canShareAcrossProfile != canShareAcrossProfile) {
+ getBaseActivity().getDisplayState().canShareAcrossProfile = canShareAcrossProfile;
+ if (!UserId.CURRENT_USER.equals(getBaseActivity().getSelectedUser())) {
+ mActionHandler.loadDocumentsForCurrentStack();
+ }
+ }
// If there are some providers and apps has the same package name, combine them as one item.
for (RootItem rootItem : otherProviders) {
@@ -448,7 +453,7 @@ public class RootsFragment extends Fragment {
}
}
- if (profileItem != null && Features.CROSS_PROFILE_TABS) {
+ if (canShareAcrossProfile && Features.CROSS_PROFILE_TABS) {
// Combine lists only if we enabled profile tab feature.
rootList.addAll(rootListOtherUser);
}
@@ -466,7 +471,7 @@ public class RootsFragment extends Fragment {
mApplicationItemList = rootList;
- if (profileItem != null && !Features.CROSS_PROFILE_TABS) {
+ if (canShareAcrossProfile && !Features.CROSS_PROFILE_TABS) {
// Add profile item if we don't support cross-profile tab.
result.add(new SpacerItem());
result.add(profileItem);
diff --git a/tests/common/com/android/documentsui/dirlist/TestEnvironment.java b/tests/common/com/android/documentsui/dirlist/TestEnvironment.java
new file mode 100644
index 000000000..2e91e0733
--- /dev/null
+++ b/tests/common/com/android/documentsui/dirlist/TestEnvironment.java
@@ -0,0 +1,96 @@
+/*
+ * 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.dirlist;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import com.android.documentsui.ActionHandler;
+import com.android.documentsui.Model;
+import com.android.documentsui.base.Features;
+import com.android.documentsui.base.State;
+import com.android.documentsui.testing.TestEnv;
+
+public final class TestEnvironment implements DocumentsAdapter.Environment {
+ private final Context testContext;
+ private final TestEnv mEnv;
+ private final ActionHandler mActionHandler;
+
+ public TestEnvironment(Context testContext, TestEnv env, ActionHandler actionHandler) {
+ this.testContext = testContext;
+ mEnv = env;
+ mActionHandler = actionHandler;
+ }
+
+ @Override
+ public Features getFeatures() {
+ return mEnv.features;
+ }
+
+ @Override
+ public ActionHandler getActionHandler() {
+ return mActionHandler;
+ }
+
+ @Override
+ public boolean isSelected(String id) {
+ return false;
+ }
+
+ @Override
+ public boolean isDocumentEnabled(String mimeType, int flags) {
+ return true;
+ }
+
+ @Override
+ public void initDocumentHolder(DocumentHolder holder) {
+ }
+
+ @Override
+ public Model getModel() {
+ return mEnv.model;
+ }
+
+ @Override
+ public State getDisplayState() {
+ return mEnv.state;
+ }
+
+ @Override
+ public boolean isInSearchMode() {
+ return false;
+ }
+
+ @Override
+ public Context getContext() {
+ return testContext;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 4;
+ }
+
+ @Override
+ public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
+ }
+
+ @Override
+ public String getCallingAppName() {
+ return "unknown";
+ }
+}
diff --git a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
index cdbf35ce2..54c563259 100644
--- a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
@@ -17,7 +17,6 @@
package com.android.documentsui.dirlist;
import android.content.Context;
-import android.database.Cursor;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
@@ -27,10 +26,8 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.MediumTest;
import com.android.documentsui.ActionHandler;
-import com.android.documentsui.Model;
import com.android.documentsui.ModelId;
import com.android.documentsui.base.DocumentInfo;
-import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestActionHandler;
import com.android.documentsui.testing.TestEnv;
@@ -53,7 +50,7 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase {
mEnv.clear();
final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY);
- DocumentsAdapter.Environment env = new TestEnvironment(testContext);
+ DocumentsAdapter.Environment env = new TestEnvironment(testContext, mEnv, mActionHandler);
mAdapter = new DirectoryAddonsAdapter(
env,
@@ -189,68 +186,6 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase {
assertTrue(mAdapter.getItemViewType(index) == type);
}
- private final class TestEnvironment implements DocumentsAdapter.Environment {
- private final Context testContext;
-
- private TestEnvironment(Context testContext) {
- this.testContext = testContext;
- }
-
- @Override
- public Features getFeatures() {
- return mEnv.features;
- }
-
- @Override
- public ActionHandler getActionHandler() { return mActionHandler; }
-
- @Override
- public boolean isSelected(String id) {
- return false;
- }
-
- @Override
- public boolean isDocumentEnabled(String mimeType, int flags) {
- return true;
- }
-
- @Override
- public void initDocumentHolder(DocumentHolder holder) {}
-
- @Override
- public Model getModel() {
- return mEnv.model;
- }
-
- @Override
- public State getDisplayState() {
- return mEnv.state;
- }
-
- @Override
- public boolean isInSearchMode() {
- return false;
- }
-
- @Override
- public Context getContext() {
- return testContext;
- }
-
- @Override
- public int getColumnCount() {
- return 4;
- }
-
- @Override
- public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {}
-
- @Override
- public String getCallingAppName() {
- return "unknown";
- }
- }
-
private static class DummyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public int getItemCount() { return 0; }
diff --git a/tests/unit/com/android/documentsui/dirlist/MessageTest.java b/tests/unit/com/android/documentsui/dirlist/MessageTest.java
new file mode 100644
index 000000000..d31f9321a
--- /dev/null
+++ b/tests/unit/com/android/documentsui/dirlist/MessageTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.dirlist;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.documentsui.CrossProfileNoPermissionException;
+import com.android.documentsui.CrossProfileQuietModeException;
+import com.android.documentsui.Model;
+import com.android.documentsui.R;
+import com.android.documentsui.base.UserId;
+import com.android.documentsui.testing.TestActionHandler;
+import com.android.documentsui.testing.TestEnv;
+import com.android.documentsui.testing.UserManagers;
+import com.android.documentsui.util.VersionUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public final class MessageTest {
+
+ private UserId mUserId = UserId.of(100);
+ private Message mInflateMessage;
+ private Context mContext;
+ private Runnable mDefaultCallback = () -> {
+ };
+ private UserManager mUserManager;
+
+ @Before
+ public void setUp() {
+ mContext = mock(Context.class);
+ mUserManager = UserManagers.create();
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
+ DocumentsAdapter.Environment env =
+ new TestEnvironment(mContext, TestEnv.create(), new TestActionHandler());
+ mInflateMessage = new Message.InflateMessage(env, mDefaultCallback);
+ }
+
+ @Test
+ public void testInflateMessage_updateToCrossProfileNoPermission() {
+ Model.Update error = new Model.Update(
+ new CrossProfileNoPermissionException(),
+ /* isRemoteActionsEnabled= */ true);
+
+ mInflateMessage.update(error);
+ assertThat(mInflateMessage.getLayout())
+ .isEqualTo(InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR);
+ assertThat(mInflateMessage.getTitleString())
+ .isEqualTo(mContext.getString(R.string.cant_share_across_profile_error_title));
+ // The value varies according to the current user. Simply assert not null.
+ assertThat(mInflateMessage.getMessageString()).isNotNull();
+ // No button for this error
+ assertThat(mInflateMessage.getButtonString()).isNull();
+ }
+
+ @Test
+ public void testInflateMessage_updateToCrossProfileQuietMode() {
+ Model.Update error = new Model.Update(
+ new CrossProfileQuietModeException(mUserId),
+ /* isRemoteActionsEnabled= */ true);
+ mInflateMessage.update(error);
+ assertThat(mInflateMessage.getLayout())
+ .isEqualTo(InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR);
+ assertThat(mInflateMessage.getTitleString())
+ .isEqualTo(mContext.getString(R.string.quiet_mode_error_title));
+ assertThat(mInflateMessage.getMessageString())
+ .isEqualTo(mContext.getString(R.string.quiet_mode_error_message));
+ if (VersionUtils.isAtLeastR()) {
+ // On R or above, we should have permission and can populate a button
+ assertThat(mInflateMessage.getButtonString()).isEqualTo(
+ mContext.getString(R.string.quiet_mode_button));
+ assertThat(mInflateMessage.mCallback).isNotNull();
+ mInflateMessage.mCallback.run();
+ verify(mUserManager, timeout(3000))
+ .requestQuietModeEnabled(false, UserHandle.of(mUserId.getIdentifier()));
+ } else {
+ assertThat(mInflateMessage.getButtonString()).isNull();
+ }
+ }
+}
diff --git a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
index 5b5d7f440..72014f63b 100644
--- a/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -17,14 +17,12 @@
package com.android.documentsui.dirlist;
import android.content.Context;
-import android.database.Cursor;
import android.test.AndroidTestCase;
import androidx.test.filters.MediumTest;
import com.android.documentsui.ActionHandler;
import com.android.documentsui.Model;
-import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestActionHandler;
import com.android.documentsui.testing.TestEnv;
@@ -45,7 +43,7 @@ public class ModelBackedDocumentsAdapterTest extends AndroidTestCase {
mEnv = TestEnv.create(AUTHORITY);
mActionHandler = new TestActionHandler();
- DocumentsAdapter.Environment env = new TestEnvironment(testContext);
+ DocumentsAdapter.Environment env = new TestEnvironment(testContext, mEnv, mActionHandler);
mAdapter = new ModelBackedDocumentsAdapter(
env,
@@ -58,66 +56,4 @@ public class ModelBackedDocumentsAdapterTest extends AndroidTestCase {
public void testItemCount() {
assertEquals(mEnv.model.getItemCount(), mAdapter.getItemCount());
}
-
- private final class TestEnvironment implements DocumentsAdapter.Environment {
- private final Context testContext;
-
- @Override
- public Features getFeatures() {
- return mEnv.features;
- }
-
- @Override
- public ActionHandler getActionHandler() { return mActionHandler; }
-
- private TestEnvironment(Context testContext) {
- this.testContext = testContext;
- }
-
- @Override
- public boolean isSelected(String id) {
- return false;
- }
-
- @Override
- public boolean isDocumentEnabled(String mimeType, int flags) {
- return true;
- }
-
- @Override
- public void initDocumentHolder(DocumentHolder holder) {}
-
- @Override
- public Model getModel() {
- return mEnv.model;
- }
-
- @Override
- public State getDisplayState() {
- return null;
- }
-
- @Override
- public boolean isInSearchMode() {
- return false;
- }
-
- @Override
- public Context getContext() {
- return testContext;
- }
-
- @Override
- public int getColumnCount() {
- return 4;
- }
-
- @Override
- public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {}
-
- @Override
- public String getCallingAppName() {
- return "unknown";
- }
- }
}