1 /* 2 * Copyright (C) 2023 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.settings.privatespace; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD; 20 21 import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS; 22 import static com.android.settings.activityembedding.EmbeddedDeepLinkUtils.tryStartMultiPaneDeepLink; 23 import static com.android.settings.password.ConfirmDeviceCredentialActivity.CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY; 24 import static com.android.settings.password.ConfirmDeviceCredentialActivity.CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY; 25 26 import android.app.ActivityOptions; 27 import android.app.KeyguardManager; 28 import android.app.PendingIntent; 29 import android.app.settings.SettingsEnums; 30 import android.content.Context; 31 import android.content.DialogInterface; 32 import android.content.Intent; 33 import android.os.Bundle; 34 import android.os.Flags; 35 import android.util.Log; 36 37 import androidx.activity.result.ActivityResult; 38 import androidx.activity.result.ActivityResultLauncher; 39 import androidx.activity.result.contract.ActivityResultContracts; 40 import androidx.annotation.Nullable; 41 import androidx.appcompat.app.AlertDialog; 42 import androidx.fragment.app.FragmentActivity; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.app.SetScreenLockDialogActivity; 46 import com.android.settings.R; 47 import com.android.settings.activityembedding.ActivityEmbeddingUtils; 48 import com.android.settings.core.SubSettingLauncher; 49 import com.android.settingslib.transition.SettingsTransitionHelper; 50 51 import com.google.android.setupdesign.util.ThemeHelper; 52 53 /** 54 * This class represents an activity responsible for user authentication before starting the private 55 * space setup flow or accessing the private space settings page if already created. Also prompts 56 * user to set a device lock if not set with an alert dialog. This can be launched using the intent 57 * com.android.settings.action.OPEN_PRIVATE_SPACE_SETTINGS. 58 */ 59 public class PrivateSpaceAuthenticationActivity extends FragmentActivity { 60 private static final String TAG = "PrivateSpaceAuthCheck"; 61 public static final String EXTRA_SHOW_PRIVATE_SPACE_UNLOCKED = 62 "extra_show_private_space_unlocked"; 63 private PrivateSpaceMaintainer mPrivateSpaceMaintainer; 64 private KeyguardManager mKeyguardManager; 65 66 private final ActivityResultLauncher<Intent> mSetDeviceLock = 67 registerForActivityResult( 68 new ActivityResultContracts.StartActivityForResult(), 69 this::onSetDeviceLockResult); 70 private final ActivityResultLauncher<Intent> mVerifyDeviceLock = 71 registerForActivityResult( 72 new ActivityResultContracts.StartActivityForResult(), this::onVerifyDeviceLock); 73 74 static class Injector { injectPrivateSpaceMaintainer(Context context)75 PrivateSpaceMaintainer injectPrivateSpaceMaintainer(Context context) { 76 return PrivateSpaceMaintainer.getInstance(context); 77 } 78 } 79 80 @Override onCreate(Bundle savedInstanceState)81 protected void onCreate(Bundle savedInstanceState) { 82 super.onCreate(savedInstanceState); 83 if (!(Flags.allowPrivateProfile() 84 && android.multiuser.Flags.enablePrivateSpaceFeatures())) { 85 finish(); 86 return; 87 } 88 89 Intent intent = getIntent(); 90 String highlightMenuKey = getString(R.string.menu_key_security); 91 if (shouldShowMultiPaneDeepLink(intent) 92 && tryStartMultiPaneDeepLink(this, intent, highlightMenuKey)) { 93 finish(); 94 return; 95 } 96 97 ThemeHelper.trySetDynamicColor(this); 98 mPrivateSpaceMaintainer = 99 new Injector().injectPrivateSpaceMaintainer(getApplicationContext()); 100 if (getKeyguardManager().isDeviceSecure()) { 101 if (savedInstanceState == null) { 102 if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { 103 unlockAndLaunchPrivateSpaceSettings(this); 104 } else { 105 authenticatePrivateSpaceEntry(); 106 } 107 } 108 } else { 109 promptToSetDeviceLock(); 110 } 111 } 112 shouldShowMultiPaneDeepLink(Intent intent)113 private boolean shouldShowMultiPaneDeepLink(Intent intent) { 114 if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { 115 return false; 116 } 117 118 // If the activity is task root, starting trampoline is needed in order to show two-pane UI. 119 // If FLAG_ACTIVITY_NEW_TASK is set, the activity will become the start of a new task on 120 // this history stack, so starting trampoline is needed in order to notify the homepage that 121 // the highlight key is changed. 122 if (!isTaskRoot() && (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { 123 return false; 124 } 125 126 // Only starts trampoline for deep links. Should return false for all the cases that 127 // Settings app starts SettingsActivity or SubSetting by itself. 128 // Other apps should send deep link intent which matches intent filter of the Activity. 129 return intent.getAction() != null; 130 } 131 132 /** Starts private space setup flow or the PS settings page on device lock authentication */ 133 @VisibleForTesting onLockAuthentication(Context context)134 public void onLockAuthentication(Context context) { 135 if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) { 136 unlockAndLaunchPrivateSpaceSettings(context); 137 } else { 138 startActivity(new Intent(context, PrivateSpaceSetupActivity.class)); 139 finish(); 140 } 141 } 142 143 @VisibleForTesting setPrivateSpaceMaintainer(Injector injector)144 public void setPrivateSpaceMaintainer(Injector injector) { 145 mPrivateSpaceMaintainer = injector.injectPrivateSpaceMaintainer(this); 146 } 147 promptToSetDeviceLock()148 private void promptToSetDeviceLock() { 149 Log.d(TAG, "Show prompt to set device lock before using private space feature"); 150 if (android.multiuser.Flags.showSetScreenLockDialog()) { 151 Intent setScreenLockPromptIntent = 152 SetScreenLockDialogActivity 153 .createBaseIntent(LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS); 154 startActivity(setScreenLockPromptIntent); 155 finish(); 156 } else { 157 new AlertDialog.Builder(this, R.style.Theme_AlertDialog) 158 .setTitle(R.string.no_device_lock_title) 159 .setMessage(R.string.no_device_lock_summary) 160 .setPositiveButton( 161 R.string.no_device_lock_action_label, 162 (DialogInterface dialog, int which) -> { 163 Log.d(TAG, "Start activity to set new device lock"); 164 mSetDeviceLock.launch(new Intent(ACTION_SET_NEW_PASSWORD)); 165 }) 166 .setNegativeButton( 167 R.string.no_device_lock_cancel, 168 (DialogInterface dialog, int which) -> finish()) 169 .setOnCancelListener( 170 (DialogInterface dialog) -> { 171 finish(); 172 }) 173 .show(); 174 } 175 } 176 getKeyguardManager()177 private KeyguardManager getKeyguardManager() { 178 if (mKeyguardManager == null) { 179 mKeyguardManager = getSystemService(KeyguardManager.class); 180 } 181 return mKeyguardManager; 182 } 183 onSetDeviceLockResult(@ullable ActivityResult result)184 private void onSetDeviceLockResult(@Nullable ActivityResult result) { 185 if (result != null) { 186 if (getKeyguardManager().isDeviceSecure()) { 187 onLockAuthentication(this); 188 } else { 189 finish(); 190 } 191 } 192 } 193 onVerifyDeviceLock(@ullable ActivityResult result)194 private void onVerifyDeviceLock(@Nullable ActivityResult result) { 195 if (result != null && result.getResultCode() == RESULT_OK) { 196 onLockAuthentication(this); 197 } else { 198 finish(); 199 } 200 } 201 unlockAndLaunchPrivateSpaceSettings(Context context)202 private void unlockAndLaunchPrivateSpaceSettings(Context context) { 203 SubSettingLauncher privateSpaceSettings = 204 new SubSettingLauncher(context) 205 .setDestination(PrivateSpaceDashboardFragment.class.getName()) 206 .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE) 207 .setSourceMetricsCategory(SettingsEnums.PRIVATE_SPACE_SETTINGS); 208 if (mPrivateSpaceMaintainer.isPrivateSpaceLocked()) { 209 ActivityOptions options = 210 ActivityOptions.makeBasic() 211 .setPendingIntentCreatorBackgroundActivityStartMode( 212 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); 213 mPrivateSpaceMaintainer.unlockPrivateSpace( 214 PendingIntent.getActivity( 215 context, /* requestCode */ 216 0, 217 privateSpaceSettings 218 .toIntent() 219 .putExtra(EXTRA_SHOW_PRIVATE_SPACE_UNLOCKED, true), 220 PendingIntent.FLAG_IMMUTABLE, 221 options.toBundle()) 222 .getIntentSender()); 223 } else { 224 Log.i(TAG, "Launch private space settings"); 225 privateSpaceSettings.launch(); 226 } 227 finish(); 228 } 229 authenticatePrivateSpaceEntry()230 private void authenticatePrivateSpaceEntry() { 231 Intent credentialIntent = mPrivateSpaceMaintainer.getPrivateProfileLockCredentialIntent(); 232 if (credentialIntent != null) { 233 if (android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt()) { 234 credentialIntent.putExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY, 235 com.android.internal.R.drawable.stat_sys_private_profile_status); 236 credentialIntent.putExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY, 237 getApplicationContext().getString( 238 com.android.internal.R.string.private_space_biometric_prompt_title 239 )); 240 } 241 mVerifyDeviceLock.launch(credentialIntent); 242 } else { 243 Log.e(TAG, "verifyCredentialIntent is null even though device lock is set"); 244 finish(); 245 } 246 } 247 } 248