/* * Copyright (C) 2021 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.car.settings.enterprise; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Dialog; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.util.IconDrawableFactory; import androidx.annotation.DrawableRes; import androidx.annotation.VisibleForTesting; import com.android.car.settings.R; import com.android.car.settings.common.Logger; import com.android.car.ui.AlertDialogBuilder; import com.android.car.ui.preference.CarUiDialogFragment; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.enterprise.ActionDisabledByAdminController; import com.android.settingslib.enterprise.ActionDisabledByAdminControllerFactory; /** * Shows a dialog explaining that an action is not enabled due to restrictions imposed by an active * device administrator. */ // TODO(b/186905050): add unit tests // TODO(b/188836559): move most of this class' logic to settingslib public final class ActionDisabledByAdminDialogFragment extends CarUiDialogFragment { public static final String DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG = "com.android.car.settings.applications.DisabledByAdminConfirmDialog"; private static final String TAG = ActionDisabledByAdminDialogFragment.class.getSimpleName(); private static final Logger LOG = new Logger(TAG); private static final String EXTRA_RESTRICTION = TAG + "_restriction"; private static final String EXTRA_RESTRICTED_PKG = TAG + "_pkg"; private static final String EXTRA_ADMIN_USER_ID = TAG + "_userId"; @VisibleForTesting String mRestriction; String mRestrictedPackage; @UserIdInt private int mAdminUserId; private ActionDisabledByAdminController mActionDisabledByAdminController; private DismissListener mDismissListener; /** * Gets the dialog for the given user and restriction. */ public static ActionDisabledByAdminDialogFragment newInstance(String restriction, @UserIdInt int userId) { return newInstance(restriction, null, userId); } /** * Gets the dialog for the given user and restriction. */ public static ActionDisabledByAdminDialogFragment newInstance(String restriction, @Nullable String restrictedPackage, @UserIdInt int userId) { ActionDisabledByAdminDialogFragment instance = new ActionDisabledByAdminDialogFragment(); instance.mRestriction = restriction; instance.mRestrictedPackage = restrictedPackage; instance.mAdminUserId = userId; return instance; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { if (savedInstanceState != null) { mRestriction = savedInstanceState.getString(EXTRA_RESTRICTION); mRestrictedPackage = savedInstanceState.getString(EXTRA_RESTRICTED_PKG); mAdminUserId = savedInstanceState.getInt(EXTRA_ADMIN_USER_ID); } return initialize(getContext()).create(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(EXTRA_RESTRICTION, mRestriction); outState.putString(EXTRA_RESTRICTED_PKG, mRestrictedPackage); outState.putInt(EXTRA_ADMIN_USER_ID, mAdminUserId); } @Override protected void onDialogClosed(boolean positiveResult) { if (mDismissListener != null) { mDismissListener.onDismiss(positiveResult); mDismissListener = null; } } /** Sets the listeners which listens for the dialog dismissed */ public void setDismissListener(DismissListener dismissListener) { mDismissListener = dismissListener; } private AlertDialogBuilder initialize(Context context) { Intent intent = getActivity().getIntent(); boolean hasValidIntent = intent != null && intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN) != null; EnforcedAdmin enforcedAdmin = hasValidIntent ? EnterpriseUtils.getEnforcedAdminFromIntent(context, intent) : EnterpriseUtils.getEnforcedAdmin(context, mAdminUserId, mRestriction, mRestrictedPackage); LOG.i("hasValidIntent: " + hasValidIntent + " enforcedAdmin: " + enforcedAdmin + " mAdminUserId: " + mAdminUserId); AlertDialogBuilder builder = new AlertDialogBuilder(context) .setPositiveButton(R.string.okay, /* listener= */ null); mActionDisabledByAdminController = ActionDisabledByAdminControllerFactory .createInstance(context, mRestriction, new DeviceAdminStringProviderImpl(context), context.getUser()); // Learn more button should launch admin policy information on the current user mActionDisabledByAdminController.initialize( new ActionDisabledLearnMoreButtonLauncherImpl(builder, /* preferredUser= */ context.getUser())); if (enforcedAdmin != null) { mActionDisabledByAdminController.updateEnforcedAdmin(enforcedAdmin, mAdminUserId); mActionDisabledByAdminController.setupLearnMoreButton(context); } initializeDialogViews(context, builder, enforcedAdmin, getEnforcementAdminUserId(enforcedAdmin)); return builder; } // NOTE: methods below were copied from phone Settings // (com.android.settings.enterprise.ActionDisabledByAdminDialogHelper), but adjusted to // use a AlertDialogBuilder directly, instead of an Activity hosting a dialog. private static @UserIdInt int getEnforcementAdminUserId(@Nullable EnforcedAdmin admin) { return admin == null || admin.user == null ? UserHandle.USER_NULL : admin.user.getIdentifier(); } private void initializeDialogViews(Context context, AlertDialogBuilder builder, @Nullable EnforcedAdmin enforcedAdmin, @UserIdInt int userId) { ComponentName admin = null; if (enforcedAdmin != null) { admin = enforcedAdmin.component; if (admin == null) { return; } mActionDisabledByAdminController.updateEnforcedAdmin(enforcedAdmin, userId); } if (isNotValidEnforcedAdmin(context, enforcedAdmin)) { admin = null; } setIcon(builder, R.drawable.ic_lock); setAdminSupportTitle(context, builder, mRestriction); if (enforcedAdmin != null) { setAdminSupportDetails(context, builder, enforcedAdmin); } } private boolean isNotValidEnforcedAdmin(Context context, EnforcedAdmin enforcedAdmin) { if (enforcedAdmin == null) { LOG.w("isNotValidEnforcedAdmin(): enforcedAdmin is null"); return true; } ComponentName admin = enforcedAdmin.component; int userId = getEnforcementAdminUserId(enforcedAdmin); if (isNotCurrentUserOrProfile(context, admin, userId) && isNotDeviceOwner(context, admin, userId)) { LOG.w("isNotValidEnforcedAdmin(): is not current user or profile/device owner"); return true; } return false; } private boolean isNotCurrentUserOrProfile(Context context, ComponentName admin, @UserIdInt int userId) { return !RestrictedLockUtilsInternal.isAdminInCurrentUserOrProfile(context, admin) || !RestrictedLockUtils.isCurrentUserOrProfile(context, userId); } private boolean isNotDeviceOwner(Context context, ComponentName admin, @UserIdInt int userId) { EnforcedAdmin deviceOwner = RestrictedLockUtilsInternal.getDeviceOwner(context); return !((deviceOwner.component).equals(admin) && userId == UserHandle.USER_SYSTEM); } private void setAdminSupportTitle(Context context, AlertDialogBuilder builder, String restriction) { builder.setTitle(mActionDisabledByAdminController.getAdminSupportTitle(restriction)); } private void setIcon(AlertDialogBuilder builder, @DrawableRes int iconId) { builder.setIcon(iconId); } private void setAdminSupportDetails(Context context, AlertDialogBuilder builder, @Nullable EnforcedAdmin enforcedAdmin) { if (enforcedAdmin == null || enforcedAdmin.component == null) { LOG.i("setAdminSupportDetails(): no admin on " + enforcedAdmin); return; } CharSequence supportMessage = null; if (isNotValidEnforcedAdmin(context, enforcedAdmin)) { enforcedAdmin.component = null; } else { if (enforcedAdmin.user == null) { enforcedAdmin.user = UserHandle.of(UserHandle.myUserId()); } if (UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID)) { supportMessage = context.getSystemService(DevicePolicyManager.class) .getShortSupportMessageForUser(enforcedAdmin.component, getEnforcementAdminUserId(enforcedAdmin)); } } CharSequence supportContentString = mActionDisabledByAdminController.getAdminSupportContentString( context, supportMessage); if (supportContentString != null) { builder.setMessage(supportContentString); } } // Copied from com.android.settings.Utils private static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory, PackageManager packageManager, String packageName, int userId) { try { ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( packageName, PackageManager.GET_META_DATA, userId); return iconDrawableFactory.getBadgedIcon(appInfo, userId); } catch (PackageManager.NameNotFoundException e) { return packageManager.getDefaultActivityIcon(); } } /** Listens to the dismiss action. */ public interface DismissListener { /** * Defines the action to take when the dialog is closed. * * @param positiveResult - whether or not the dialog was dismissed because of a positive * button press. */ void onDismiss(boolean positiveResult); } }