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 android.Manifest; 20 import android.app.AuthenticationRequiredException; 21 import android.content.pm.PackageManager; 22 import android.content.res.Resources; 23 import android.graphics.drawable.Drawable; 24 25 import androidx.annotation.Nullable; 26 27 import com.android.documentsui.CrossProfileException; 28 import com.android.documentsui.CrossProfileNoPermissionException; 29 import com.android.documentsui.CrossProfileQuietModeException; 30 import com.android.documentsui.DocumentsApplication; 31 import com.android.documentsui.Metrics; 32 import com.android.documentsui.Model.Update; 33 import com.android.documentsui.R; 34 import com.android.documentsui.base.RootInfo; 35 import com.android.documentsui.base.State; 36 import com.android.documentsui.base.UserId; 37 import com.android.documentsui.dirlist.DocumentsAdapter.Environment; 38 39 /** 40 * Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}. 41 */ 42 43 abstract class Message { 44 protected final Environment mEnv; 45 // If the message has a button, this will be the default button call back. 46 protected final Runnable mDefaultCallback; 47 // If a message has a new callback when updated, this field should be updated. 48 protected @Nullable Runnable mCallback; 49 50 private @Nullable CharSequence mMessageTitle; 51 private @Nullable CharSequence mMessageString; 52 private @Nullable CharSequence mButtonString; 53 private @Nullable Drawable mIcon; 54 private boolean mShouldShow = false; 55 protected boolean mShouldKeep = false; 56 protected int mLayout; 57 Message(Environment env, Runnable defaultCallback)58 Message(Environment env, Runnable defaultCallback) { 59 mEnv = env; 60 mDefaultCallback = defaultCallback; 61 } 62 update(Update Event)63 abstract void update(Update Event); 64 update(@ullable CharSequence messageTitle, CharSequence messageString, @Nullable CharSequence buttonString, Drawable icon)65 protected void update(@Nullable CharSequence messageTitle, CharSequence messageString, 66 @Nullable CharSequence buttonString, Drawable icon) { 67 if (messageString == null) { 68 return; 69 } 70 mMessageTitle = messageTitle; 71 mMessageString = messageString; 72 mButtonString = buttonString; 73 mIcon = icon; 74 mShouldShow = true; 75 } 76 reset()77 void reset() { 78 mMessageString = null; 79 mIcon = null; 80 mShouldShow = false; 81 mLayout = 0; 82 } 83 runCallback()84 void runCallback() { 85 if (mCallback != null) { 86 mCallback.run(); 87 } else { 88 mDefaultCallback.run(); 89 } 90 } 91 getIcon()92 Drawable getIcon() { 93 return mIcon; 94 } 95 getLayout()96 int getLayout() { 97 return mLayout; 98 } 99 shouldShow()100 boolean shouldShow() { 101 return mShouldShow; 102 } 103 104 /** 105 * Return this message should keep showing or not. 106 * @return true if this message should keep showing. 107 */ shouldKeep()108 boolean shouldKeep() { 109 return mShouldKeep; 110 } 111 getTitleString()112 CharSequence getTitleString() { 113 return mMessageTitle; 114 } 115 getMessageString()116 CharSequence getMessageString() { 117 return mMessageString; 118 } 119 getButtonString()120 CharSequence getButtonString() { 121 return mButtonString; 122 } 123 124 final static class HeaderMessage extends Message { 125 126 private static final String TAG = "HeaderMessage"; 127 HeaderMessage(Environment env, Runnable callback)128 HeaderMessage(Environment env, Runnable callback) { 129 super(env, callback); 130 } 131 132 @Override update(Update event)133 void update(Update event) { 134 reset(); 135 // Error gets first dibs ... for now 136 // TODO: These should be different Message objects getting updated instead of 137 // overwriting. 138 if (event.hasAuthenticationException()) { 139 updateToAuthenticationExceptionHeader(event); 140 } else if (mEnv.getModel().error != null) { 141 update(null, mEnv.getModel().error, null, 142 mEnv.getContext().getDrawable(R.drawable.ic_dialog_alert)); 143 } else if (mEnv.getModel().info != null) { 144 update(null, mEnv.getModel().info, null, 145 mEnv.getContext().getDrawable(R.drawable.ic_dialog_info)); 146 } else if (mEnv.getDisplayState().action == State.ACTION_OPEN_TREE 147 && mEnv.getDisplayState().stack.peek() != null 148 && mEnv.getDisplayState().stack.peek().isBlockedFromTree() 149 && mEnv.getDisplayState().restrictScopeStorage) { 150 updateBlockFromTreeMessage(); 151 mCallback = () -> { 152 mEnv.getActionHandler().showCreateDirectoryDialog(); 153 }; 154 } 155 } 156 updateToAuthenticationExceptionHeader(Update event)157 private void updateToAuthenticationExceptionHeader(Update event) { 158 assert(mEnv.getFeatures().isRemoteActionsEnabled()); 159 160 RootInfo root = mEnv.getDisplayState().stack.getRoot(); 161 String appName = DocumentsApplication.getProvidersCache( 162 mEnv.getContext()).getApplicationName(root.userId, root.authority); 163 update(null, mEnv.getContext().getString(R.string.authentication_required, appName), 164 mEnv.getContext().getResources().getText(R.string.sign_in), 165 mEnv.getContext().getDrawable(R.drawable.ic_dialog_info)); 166 mCallback = () -> { 167 AuthenticationRequiredException exception = 168 (AuthenticationRequiredException) event.getException(); 169 mEnv.getActionHandler().startAuthentication(exception.getUserAction()); 170 }; 171 } 172 updateBlockFromTreeMessage()173 private void updateBlockFromTreeMessage() { 174 mShouldKeep = true; 175 update(mEnv.getContext().getString(R.string.directory_blocked_header_title), 176 mEnv.getContext().getString(R.string.directory_blocked_header_subtitle), 177 mEnv.getContext().getString(R.string.create_new_folder_button), 178 mEnv.getContext().getDrawable(R.drawable.ic_dialog_info)); 179 } 180 } 181 182 final static class InflateMessage extends Message { 183 184 private final boolean mCanModifyQuietMode; 185 InflateMessage(Environment env, Runnable callback)186 InflateMessage(Environment env, Runnable callback) { 187 super(env, callback); 188 mCanModifyQuietMode = 189 mEnv.getContext().checkSelfPermission(Manifest.permission.MODIFY_QUIET_MODE) 190 == PackageManager.PERMISSION_GRANTED; 191 } 192 193 @Override update(Update event)194 void update(Update event) { 195 reset(); 196 if (event.hasCrossProfileException()) { 197 CrossProfileException e = (CrossProfileException) event.getException(); 198 Metrics.logCrossProfileEmptyState(e); 199 if (e instanceof CrossProfileQuietModeException) { 200 updateToQuietModeErrorMessage( 201 ((CrossProfileQuietModeException) event.getException()).mUserId); 202 } else if (event.getException() instanceof CrossProfileNoPermissionException) { 203 updateToCrossProfileNoPermissionErrorMessage(); 204 } else { 205 updateToInflatedErrorMessage(); 206 } 207 } else if (event.hasException() && !event.hasAuthenticationException()) { 208 updateToInflatedErrorMessage(); 209 } else if (event.hasAuthenticationException()) { 210 updateToCantDisplayContentMessage(); 211 } else if (mEnv.getModel().getModelIds().length == 0) { 212 updateToInflatedEmptyMessage(); 213 } 214 } 215 updateToQuietModeErrorMessage(UserId userId)216 private void updateToQuietModeErrorMessage(UserId userId) { 217 mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR; 218 CharSequence buttonText = null; 219 if (mCanModifyQuietMode) { 220 buttonText = mEnv.getContext().getResources().getText(R.string.quiet_mode_button); 221 mCallback = () -> mEnv.getActionHandler().requestQuietModeDisabled( 222 mEnv.getDisplayState().stack.getRoot(), userId); 223 } 224 update( 225 mEnv.getContext().getResources().getText(R.string.quiet_mode_error_title), 226 /* messageString= */ "", 227 buttonText, 228 mEnv.getContext().getDrawable(R.drawable.work_off)); 229 } 230 updateToCrossProfileNoPermissionErrorMessage()231 private void updateToCrossProfileNoPermissionErrorMessage() { 232 mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR; 233 boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); 234 update(getCrossProfileNoPermissionErrorTitle(), 235 getCrossProfileNoPermissionErrorMessage(), 236 /* buttonString= */ null, 237 mEnv.getContext().getDrawable(R.drawable.share_off)); 238 } 239 getCrossProfileNoPermissionErrorTitle()240 private CharSequence getCrossProfileNoPermissionErrorTitle() { 241 boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); 242 Resources res = mEnv.getContext().getResources(); 243 switch (mEnv.getDisplayState().action) { 244 case State.ACTION_GET_CONTENT: 245 case State.ACTION_OPEN: 246 case State.ACTION_OPEN_TREE: 247 return res.getText(currentUserIsSystem 248 ? R.string.cant_select_work_files_error_title 249 : R.string.cant_select_personal_files_error_title); 250 case State.ACTION_CREATE: 251 return res.getText(currentUserIsSystem 252 ? R.string.cant_save_to_work_error_title 253 : R.string.cant_save_to_personal_error_title); 254 } 255 return res.getText(R.string.cross_profile_action_not_allowed_title); 256 } 257 getCrossProfileNoPermissionErrorMessage()258 private CharSequence getCrossProfileNoPermissionErrorMessage() { 259 boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); 260 Resources res = mEnv.getContext().getResources(); 261 switch (mEnv.getDisplayState().action) { 262 case State.ACTION_GET_CONTENT: 263 case State.ACTION_OPEN: 264 case State.ACTION_OPEN_TREE: 265 return res.getText(currentUserIsSystem 266 ? R.string.cant_select_work_files_error_message 267 : R.string.cant_select_personal_files_error_message); 268 case State.ACTION_CREATE: 269 return res.getText(currentUserIsSystem 270 ? R.string.cant_save_to_work_error_message 271 : R.string.cant_save_to_personal_error_message); 272 } 273 return res.getText(R.string.cross_profile_action_not_allowed_message); 274 } 275 updateToInflatedErrorMessage()276 private void updateToInflatedErrorMessage() { 277 update(null, mEnv.getContext().getResources().getText(R.string.query_error), null, 278 mEnv.getContext().getDrawable(R.drawable.hourglass)); 279 } 280 updateToCantDisplayContentMessage()281 private void updateToCantDisplayContentMessage() { 282 update(null, mEnv.getContext().getResources().getText(R.string.cant_display_content), 283 null, mEnv.getContext().getDrawable(R.drawable.empty)); 284 } 285 updateToInflatedEmptyMessage()286 private void updateToInflatedEmptyMessage() { 287 final CharSequence message; 288 if (mEnv.isInSearchMode()) { 289 message = String.format( 290 String.valueOf( 291 mEnv.getContext().getResources().getText(R.string.no_results)), 292 mEnv.getDisplayState().stack.getRoot().title); 293 } else { 294 message = mEnv.getContext().getResources().getText(R.string.empty); 295 } 296 update(null, message, null, mEnv.getContext().getDrawable(R.drawable.empty)); 297 } 298 } 299 } 300