• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.dirlist;
18 
19 import static com.android.documentsui.DevicePolicyResources.Drawables.Style.OUTLINE;
20 import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_OFF_ICON;
21 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_PERSONAL_MESSAGE;
22 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_PERSONAL_TITLE;
23 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_WORK_MESSAGE;
24 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_WORK_TITLE;
25 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_PERSONAL_FILES_MESSAGE;
26 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_PERSONAL_FILES_TITLE;
27 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_MESSAGE;
28 import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_TITLE;
29 import static com.android.documentsui.DevicePolicyResources.Strings.CROSS_PROFILE_NOT_ALLOWED_MESSAGE;
30 import static com.android.documentsui.DevicePolicyResources.Strings.CROSS_PROFILE_NOT_ALLOWED_TITLE;
31 import static com.android.documentsui.DevicePolicyResources.Strings.WORK_PROFILE_OFF_ENABLE_BUTTON;
32 import static com.android.documentsui.DevicePolicyResources.Strings.WORK_PROFILE_OFF_ERROR_TITLE;
33 
34 import android.Manifest;
35 import android.app.AuthenticationRequiredException;
36 import android.app.admin.DevicePolicyManager;
37 import android.content.pm.PackageManager;
38 import android.graphics.drawable.Drawable;
39 import android.os.Build;
40 
41 import androidx.annotation.Nullable;
42 import androidx.annotation.RequiresApi;
43 
44 import com.android.documentsui.CrossProfileException;
45 import com.android.documentsui.CrossProfileNoPermissionException;
46 import com.android.documentsui.CrossProfileQuietModeException;
47 import com.android.documentsui.DocumentsApplication;
48 import com.android.documentsui.Metrics;
49 import com.android.documentsui.Model.Update;
50 import com.android.documentsui.R;
51 import com.android.documentsui.base.RootInfo;
52 import com.android.documentsui.base.State;
53 import com.android.documentsui.base.UserId;
54 import com.android.documentsui.dirlist.DocumentsAdapter.Environment;
55 import com.android.modules.utils.build.SdkLevel;
56 
57 /**
58  * Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}.
59  */
60 
61 abstract class Message {
62     protected final Environment mEnv;
63     // If the message has a button, this will be the default button call back.
64     protected final Runnable mDefaultCallback;
65     // If a message has a new callback when updated, this field should be updated.
66     protected @Nullable Runnable mCallback;
67 
68     private @Nullable CharSequence mMessageTitle;
69     private @Nullable CharSequence mMessageString;
70     private @Nullable CharSequence mButtonString;
71     private @Nullable Drawable mIcon;
72     private boolean mShouldShow = false;
73     protected boolean mShouldKeep = false;
74     protected int mLayout;
75 
Message(Environment env, Runnable defaultCallback)76     Message(Environment env, Runnable defaultCallback) {
77         mEnv = env;
78         mDefaultCallback = defaultCallback;
79     }
80 
update(Update Event)81     abstract void update(Update Event);
82 
update(@ullable CharSequence messageTitle, CharSequence messageString, @Nullable CharSequence buttonString, Drawable icon)83     protected void update(@Nullable CharSequence messageTitle, CharSequence messageString,
84             @Nullable CharSequence buttonString, Drawable icon) {
85         if (messageString == null) {
86             return;
87         }
88         mMessageTitle = messageTitle;
89         mMessageString = messageString;
90         mButtonString = buttonString;
91         mIcon = icon;
92         mShouldShow = true;
93     }
94 
reset()95     void reset() {
96         mMessageString = null;
97         mIcon = null;
98         mShouldShow = false;
99         mLayout = 0;
100     }
101 
runCallback()102     void runCallback() {
103         if (mCallback != null) {
104             mCallback.run();
105         } else {
106             mDefaultCallback.run();
107         }
108     }
109 
getIcon()110     Drawable getIcon() {
111         return mIcon;
112     }
113 
getLayout()114     int getLayout() {
115         return mLayout;
116     }
117 
shouldShow()118     boolean shouldShow() {
119         return mShouldShow;
120     }
121 
122     /**
123      * Return this message should keep showing or not.
124      * @return true if this message should keep showing.
125      */
shouldKeep()126     boolean shouldKeep() {
127         return mShouldKeep;
128     }
129 
getTitleString()130     CharSequence getTitleString() {
131         return mMessageTitle;
132     }
133 
getMessageString()134     CharSequence getMessageString() {
135         return mMessageString;
136     }
137 
getButtonString()138     CharSequence getButtonString() {
139         return mButtonString;
140     }
141 
142     final static class HeaderMessage extends Message {
143 
144         private static final String TAG = "HeaderMessage";
145 
HeaderMessage(Environment env, Runnable callback)146         HeaderMessage(Environment env, Runnable callback) {
147             super(env, callback);
148         }
149 
150         @Override
update(Update event)151         void update(Update event) {
152             reset();
153             // Error gets first dibs ... for now
154             // TODO: These should be different Message objects getting updated instead of
155             // overwriting.
156             if (event.hasAuthenticationException()) {
157                 updateToAuthenticationExceptionHeader(event);
158             } else if (mEnv.getModel().error != null) {
159                 update(null, mEnv.getModel().error, null,
160                         mEnv.getContext().getDrawable(R.drawable.ic_dialog_alert));
161             } else if (mEnv.getModel().info != null) {
162                 update(null, mEnv.getModel().info, null,
163                         mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
164             } else if (mEnv.getDisplayState().action == State.ACTION_OPEN_TREE
165                     && mEnv.getDisplayState().stack.peek() != null
166                     && mEnv.getDisplayState().stack.peek().isBlockedFromTree()
167                     && mEnv.getDisplayState().restrictScopeStorage) {
168                 updateBlockFromTreeMessage();
169                 mCallback = () -> {
170                     mEnv.getActionHandler().showCreateDirectoryDialog();
171                 };
172             }
173         }
174 
updateToAuthenticationExceptionHeader(Update event)175         private void updateToAuthenticationExceptionHeader(Update event) {
176             assert(mEnv.getFeatures().isRemoteActionsEnabled());
177 
178             RootInfo root = mEnv.getDisplayState().stack.getRoot();
179             String appName = DocumentsApplication.getProvidersCache(
180                     mEnv.getContext()).getApplicationName(root.userId, root.authority);
181             update(null, mEnv.getContext().getString(R.string.authentication_required, appName),
182                     mEnv.getContext().getResources().getText(R.string.sign_in),
183                     mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
184             mCallback = () -> {
185                 AuthenticationRequiredException exception =
186                         (AuthenticationRequiredException) event.getException();
187                 mEnv.getActionHandler().startAuthentication(exception.getUserAction());
188             };
189         }
190 
updateBlockFromTreeMessage()191         private void updateBlockFromTreeMessage() {
192             mShouldKeep = true;
193             update(mEnv.getContext().getString(R.string.directory_blocked_header_title),
194                     mEnv.getContext().getString(R.string.directory_blocked_header_subtitle),
195                     mEnv.getContext().getString(R.string.create_new_folder_button),
196                     mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
197         }
198     }
199 
200     final static class InflateMessage extends Message {
201 
202         private final boolean mCanModifyQuietMode;
203 
InflateMessage(Environment env, Runnable callback)204         InflateMessage(Environment env, Runnable callback) {
205             super(env, callback);
206             mCanModifyQuietMode =
207                     mEnv.getContext().checkSelfPermission(Manifest.permission.MODIFY_QUIET_MODE)
208                             == PackageManager.PERMISSION_GRANTED;
209         }
210 
211         @Override
update(Update event)212         void update(Update event) {
213             reset();
214             if (event.hasCrossProfileException()) {
215                 CrossProfileException e = (CrossProfileException) event.getException();
216                 Metrics.logCrossProfileEmptyState(e);
217                 if (e instanceof CrossProfileQuietModeException) {
218                     updateToQuietModeErrorMessage(
219                             ((CrossProfileQuietModeException) event.getException()).mUserId);
220                 } else if (event.getException() instanceof CrossProfileNoPermissionException) {
221                     updateToCrossProfileNoPermissionErrorMessage();
222                 } else {
223                     updateToInflatedErrorMessage();
224                 }
225             } else if (event.hasException() && !event.hasAuthenticationException()) {
226                 updateToInflatedErrorMessage();
227             } else if (event.hasAuthenticationException()) {
228                 updateToCantDisplayContentMessage();
229             } else if (mEnv.getModel().getModelIds().length == 0) {
230                 updateToInflatedEmptyMessage();
231             }
232         }
233 
updateToQuietModeErrorMessage(UserId userId)234         private void updateToQuietModeErrorMessage(UserId userId) {
235             mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
236             CharSequence buttonText = null;
237             if (mCanModifyQuietMode) {
238                 buttonText = getEnterpriseString(
239                         WORK_PROFILE_OFF_ENABLE_BUTTON, R.string.quiet_mode_button);
240                 mCallback = () -> mEnv.getActionHandler().requestQuietModeDisabled(
241                         mEnv.getDisplayState().stack.getRoot(), userId);
242             }
243             update(
244                     getEnterpriseString(
245                             WORK_PROFILE_OFF_ERROR_TITLE, R.string.quiet_mode_error_title),
246                     /* messageString= */ "",
247                     buttonText,
248                     getWorkProfileOffIcon());
249         }
250 
updateToCrossProfileNoPermissionErrorMessage()251         private void updateToCrossProfileNoPermissionErrorMessage() {
252             mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
253             update(getCrossProfileNoPermissionErrorTitle(),
254                     getCrossProfileNoPermissionErrorMessage(),
255                     /* buttonString= */ null,
256                     mEnv.getContext().getDrawable(R.drawable.share_off));
257         }
258 
getCrossProfileNoPermissionErrorTitle()259         private CharSequence getCrossProfileNoPermissionErrorTitle() {
260             boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem();
261             switch (mEnv.getDisplayState().action) {
262                 case State.ACTION_GET_CONTENT:
263                 case State.ACTION_OPEN:
264                 case State.ACTION_OPEN_TREE:
265                     return currentUserIsSystem
266                             ? getEnterpriseString(
267                                     CANT_SELECT_WORK_FILES_TITLE,
268                                     R.string.cant_select_work_files_error_title)
269                             : getEnterpriseString(
270                                     CANT_SELECT_PERSONAL_FILES_TITLE,
271                                     R.string.cant_select_personal_files_error_title);
272                 case State.ACTION_CREATE:
273                     return currentUserIsSystem
274                             ? getEnterpriseString(
275                                     CANT_SAVE_TO_WORK_TITLE, R.string.cant_save_to_work_error_title)
276                             : getEnterpriseString(
277                                     CANT_SAVE_TO_PERSONAL_TITLE,
278                                     R.string.cant_save_to_personal_error_title);
279             }
280             return getEnterpriseString(
281                     CROSS_PROFILE_NOT_ALLOWED_TITLE,
282                     R.string.cross_profile_action_not_allowed_title);
283         }
284 
getCrossProfileNoPermissionErrorMessage()285         private CharSequence getCrossProfileNoPermissionErrorMessage() {
286             boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem();
287             switch (mEnv.getDisplayState().action) {
288                 case State.ACTION_GET_CONTENT:
289                 case State.ACTION_OPEN:
290                 case State.ACTION_OPEN_TREE:
291                     return currentUserIsSystem
292                             ? getEnterpriseString(
293                                     CANT_SELECT_WORK_FILES_MESSAGE,
294                                     R.string.cant_select_work_files_error_message)
295                             : getEnterpriseString(
296                                     CANT_SELECT_PERSONAL_FILES_MESSAGE,
297                                     R.string.cant_select_personal_files_error_message);
298                 case State.ACTION_CREATE:
299                     return currentUserIsSystem
300                             ? getEnterpriseString(
301                                     CANT_SAVE_TO_WORK_MESSAGE,
302                                     R.string.cant_save_to_work_error_message)
303                             : getEnterpriseString(
304                                     CANT_SAVE_TO_PERSONAL_MESSAGE,
305                                     R.string.cant_save_to_personal_error_message);
306             }
307             return getEnterpriseString(
308                     CROSS_PROFILE_NOT_ALLOWED_MESSAGE,
309                     R.string.cross_profile_action_not_allowed_message);
310         }
311 
updateToInflatedErrorMessage()312         private void updateToInflatedErrorMessage() {
313             update(null, mEnv.getContext().getResources().getText(R.string.query_error), null,
314                     mEnv.getContext().getDrawable(R.drawable.hourglass));
315         }
316 
updateToCantDisplayContentMessage()317         private void updateToCantDisplayContentMessage() {
318             update(null, mEnv.getContext().getResources().getText(R.string.cant_display_content),
319                     null, mEnv.getContext().getDrawable(R.drawable.empty));
320         }
321 
updateToInflatedEmptyMessage()322         private void updateToInflatedEmptyMessage() {
323             final CharSequence message;
324             if (mEnv.isInSearchMode()) {
325                 message = String.format(
326                         String.valueOf(
327                                 mEnv.getContext().getResources().getText(R.string.no_results)),
328                         mEnv.getDisplayState().stack.getRoot().title);
329             } else {
330                 message = mEnv.getContext().getResources().getText(R.string.empty);
331             }
332             update(null, message, null, mEnv.getContext().getDrawable(R.drawable.empty));
333         }
334 
getEnterpriseString(String updatableStringId, int defaultStringId)335         private String getEnterpriseString(String updatableStringId, int defaultStringId) {
336             if (SdkLevel.isAtLeastT()) {
337                 return getUpdatableEnterpriseString(updatableStringId, defaultStringId);
338             } else {
339                 return mEnv.getContext().getString(defaultStringId);
340             }
341         }
342 
343         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUpdatableEnterpriseString(String updatableStringId, int defaultStringId)344         private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) {
345             DevicePolicyManager dpm = mEnv.getContext().getSystemService(
346                     DevicePolicyManager.class);
347             return dpm.getResources().getString(
348                     updatableStringId, () -> mEnv.getContext().getString(defaultStringId));
349         }
350 
getWorkProfileOffIcon()351         private Drawable getWorkProfileOffIcon() {
352             if (SdkLevel.isAtLeastT()) {
353                 return getUpdatableWorkProfileIcon();
354             } else {
355                 return mEnv.getContext().getDrawable(R.drawable.work_off);
356             }
357         }
358 
359         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
getUpdatableWorkProfileIcon()360         private Drawable getUpdatableWorkProfileIcon() {
361             DevicePolicyManager dpm = mEnv.getContext().getSystemService(
362                     DevicePolicyManager.class);
363             return dpm.getResources().getDrawable(
364                     WORK_PROFILE_OFF_ICON, OUTLINE,
365                     () -> mEnv.getContext().getDrawable(R.drawable.work_off));
366         }
367     }
368 }
369