blob: 6ba5ea388b4424cdcfb3029f9d619063f694f931 [file] [log] [blame]
/*
* Copyright (C) 2016 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.android.documentsui.DevicePolicyResources.Drawables.Style.OUTLINE;
import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_OFF_ICON;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_PERSONAL_MESSAGE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_PERSONAL_TITLE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_WORK_MESSAGE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SAVE_TO_WORK_TITLE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_PERSONAL_FILES_MESSAGE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_PERSONAL_FILES_TITLE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_MESSAGE;
import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_TITLE;
import static com.android.documentsui.DevicePolicyResources.Strings.CROSS_PROFILE_NOT_ALLOWED_MESSAGE;
import static com.android.documentsui.DevicePolicyResources.Strings.CROSS_PROFILE_NOT_ALLOWED_TITLE;
import static com.android.documentsui.DevicePolicyResources.Strings.WORK_PROFILE_OFF_ENABLE_BUTTON;
import static com.android.documentsui.DevicePolicyResources.Strings.WORK_PROFILE_OFF_ERROR_TITLE;
import android.Manifest;
import android.app.AuthenticationRequiredException;
import android.app.admin.DevicePolicyManager;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.documentsui.CrossProfileException;
import com.android.documentsui.CrossProfileNoPermissionException;
import com.android.documentsui.CrossProfileQuietModeException;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Metrics;
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;
import com.android.documentsui.util.VersionUtils;
/**
* Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}.
*/
abstract class Message {
protected final Environment mEnv;
// If the message has a button, this will be the default button call back.
protected final Runnable mDefaultCallback;
// If a message has a new callback when updated, this field should be updated.
protected @Nullable Runnable mCallback;
private @Nullable CharSequence mMessageTitle;
private @Nullable CharSequence mMessageString;
private @Nullable CharSequence mButtonString;
private @Nullable Drawable mIcon;
private boolean mShouldShow = false;
protected boolean mShouldKeep = false;
protected int mLayout;
Message(Environment env, Runnable defaultCallback) {
mEnv = env;
mDefaultCallback = defaultCallback;
}
abstract void update(Update Event);
protected void update(@Nullable CharSequence messageTitle, CharSequence messageString,
@Nullable CharSequence buttonString, Drawable icon) {
if (messageString == null) {
return;
}
mMessageTitle = messageTitle;
mMessageString = messageString;
mButtonString = buttonString;
mIcon = icon;
mShouldShow = true;
}
void reset() {
mMessageString = null;
mIcon = null;
mShouldShow = false;
mLayout = 0;
}
void runCallback() {
if (mCallback != null) {
mCallback.run();
} else {
mDefaultCallback.run();
}
}
Drawable getIcon() {
return mIcon;
}
int getLayout() {
return mLayout;
}
boolean shouldShow() {
return mShouldShow;
}
/**
* Return this message should keep showing or not.
* @return true if this message should keep showing.
*/
boolean shouldKeep() {
return mShouldKeep;
}
CharSequence getTitleString() {
return mMessageTitle;
}
CharSequence getMessageString() {
return mMessageString;
}
CharSequence getButtonString() {
return mButtonString;
}
final static class HeaderMessage extends Message {
private static final String TAG = "HeaderMessage";
HeaderMessage(Environment env, Runnable callback) {
super(env, callback);
}
@Override
void update(Update event) {
reset();
// Error gets first dibs ... for now
// TODO: These should be different Message objects getting updated instead of
// overwriting.
if (event.hasAuthenticationException()) {
updateToAuthenticationExceptionHeader(event);
} else if (mEnv.getModel().error != null) {
update(null, mEnv.getModel().error, null,
mEnv.getContext().getDrawable(R.drawable.ic_dialog_alert));
} else if (mEnv.getModel().info != null) {
update(null, mEnv.getModel().info, null,
mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
} else if (mEnv.getDisplayState().action == State.ACTION_OPEN_TREE
&& mEnv.getDisplayState().stack.peek() != null
&& mEnv.getDisplayState().stack.peek().isBlockedFromTree()
&& mEnv.getDisplayState().restrictScopeStorage) {
updateBlockFromTreeMessage();
mCallback = () -> {
mEnv.getActionHandler().showCreateDirectoryDialog();
};
}
}
private void updateToAuthenticationExceptionHeader(Update event) {
assert(mEnv.getFeatures().isRemoteActionsEnabled());
RootInfo root = mEnv.getDisplayState().stack.getRoot();
String appName = DocumentsApplication.getProvidersCache(
mEnv.getContext()).getApplicationName(root.userId, root.authority);
update(null, mEnv.getContext().getString(R.string.authentication_required, appName),
mEnv.getContext().getResources().getText(R.string.sign_in),
mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
mCallback = () -> {
AuthenticationRequiredException exception =
(AuthenticationRequiredException) event.getException();
mEnv.getActionHandler().startAuthentication(exception.getUserAction());
};
}
private void updateBlockFromTreeMessage() {
mShouldKeep = true;
update(mEnv.getContext().getString(R.string.directory_blocked_header_title),
mEnv.getContext().getString(R.string.directory_blocked_header_subtitle),
mEnv.getContext().getString(R.string.create_new_folder_button),
mEnv.getContext().getDrawable(R.drawable.ic_dialog_info));
}
}
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()) {
CrossProfileException e = (CrossProfileException) event.getException();
Metrics.logCrossProfileEmptyState(e);
if (e 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()) {
updateToCantDisplayContentMessage();
} else if (mEnv.getModel().getModelIds().length == 0) {
updateToInflatedEmptyMessage();
}
}
private void updateToQuietModeErrorMessage(UserId userId) {
mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
CharSequence buttonText = null;
if (mCanModifyQuietMode) {
buttonText = getEnterpriseString(
WORK_PROFILE_OFF_ENABLE_BUTTON, R.string.quiet_mode_button);
mCallback = () -> mEnv.getActionHandler().requestQuietModeDisabled(
mEnv.getDisplayState().stack.getRoot(), userId);
}
update(
getEnterpriseString(
WORK_PROFILE_OFF_ERROR_TITLE, R.string.quiet_mode_error_title),
/* messageString= */ "",
buttonText,
getWorkProfileOffIcon());
}
private void updateToCrossProfileNoPermissionErrorMessage() {
mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR;
update(getCrossProfileNoPermissionErrorTitle(),
getCrossProfileNoPermissionErrorMessage(),
/* buttonString= */ null,
mEnv.getContext().getDrawable(R.drawable.share_off));
}
private CharSequence getCrossProfileNoPermissionErrorTitle() {
boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem();
switch (mEnv.getDisplayState().action) {
case State.ACTION_GET_CONTENT:
case State.ACTION_OPEN:
case State.ACTION_OPEN_TREE:
return currentUserIsSystem
? getEnterpriseString(
CANT_SELECT_WORK_FILES_TITLE,
R.string.cant_select_work_files_error_title)
: getEnterpriseString(
CANT_SELECT_PERSONAL_FILES_TITLE,
R.string.cant_select_personal_files_error_title);
case State.ACTION_CREATE:
return currentUserIsSystem
? getEnterpriseString(
CANT_SAVE_TO_WORK_TITLE, R.string.cant_save_to_work_error_title)
: getEnterpriseString(
CANT_SAVE_TO_PERSONAL_TITLE,
R.string.cant_save_to_personal_error_title);
}
return getEnterpriseString(
CROSS_PROFILE_NOT_ALLOWED_TITLE,
R.string.cross_profile_action_not_allowed_title);
}
private CharSequence getCrossProfileNoPermissionErrorMessage() {
boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem();
switch (mEnv.getDisplayState().action) {
case State.ACTION_GET_CONTENT:
case State.ACTION_OPEN:
case State.ACTION_OPEN_TREE:
return currentUserIsSystem
? getEnterpriseString(
CANT_SELECT_WORK_FILES_MESSAGE,
R.string.cant_select_work_files_error_message)
: getEnterpriseString(
CANT_SELECT_PERSONAL_FILES_MESSAGE,
R.string.cant_select_personal_files_error_message);
case State.ACTION_CREATE:
return currentUserIsSystem
? getEnterpriseString(
CANT_SAVE_TO_WORK_MESSAGE,
R.string.cant_save_to_work_error_message)
: getEnterpriseString(
CANT_SAVE_TO_PERSONAL_MESSAGE,
R.string.cant_save_to_personal_error_message);
}
return getEnterpriseString(
CROSS_PROFILE_NOT_ALLOWED_MESSAGE,
R.string.cross_profile_action_not_allowed_message);
}
private void updateToInflatedErrorMessage() {
update(null, mEnv.getContext().getResources().getText(R.string.query_error), null,
mEnv.getContext().getDrawable(R.drawable.hourglass));
}
private void updateToCantDisplayContentMessage() {
update(null, mEnv.getContext().getResources().getText(R.string.cant_display_content),
null, mEnv.getContext().getDrawable(R.drawable.empty));
}
private void updateToInflatedEmptyMessage() {
final CharSequence message;
if (mEnv.isInSearchMode()) {
message = String.format(
String.valueOf(
mEnv.getContext().getResources().getText(R.string.no_results)),
mEnv.getDisplayState().stack.getRoot().title);
} else {
message = mEnv.getContext().getResources().getText(R.string.empty);
}
update(null, message, null, mEnv.getContext().getDrawable(R.drawable.empty));
}
private String getEnterpriseString(String updatableStringId, int defaultStringId) {
if (VersionUtils.isAtLeastT()) {
return getUpdatableEnterpriseString(updatableStringId, defaultStringId);
} else {
return mEnv.getContext().getString(defaultStringId);
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private String getUpdatableEnterpriseString(String updatableStringId, int defaultStringId) {
DevicePolicyManager dpm = mEnv.getContext().getSystemService(
DevicePolicyManager.class);
return dpm.getResources().getString(
updatableStringId, () -> mEnv.getContext().getString(defaultStringId));
}
private Drawable getWorkProfileOffIcon() {
if (VersionUtils.isAtLeastT()) {
return getUpdatableWorkProfileIcon();
} else {
return mEnv.getContext().getDrawable(R.drawable.work_off);
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private Drawable getUpdatableWorkProfileIcon() {
DevicePolicyManager dpm = mEnv.getContext().getSystemService(
DevicePolicyManager.class);
return dpm.getResources().getDrawable(
WORK_PROFILE_OFF_ICON, OUTLINE,
() -> mEnv.getContext().getDrawable(R.drawable.work_off));
}
}
}