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