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.onelock;
18 
19 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
20 import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
21 import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST;
22 import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST;
23 
24 import android.app.Activity;
25 import android.app.AlertDialog;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.util.Log;
33 
34 import androidx.annotation.Nullable;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceScreen;
37 
38 import com.android.internal.widget.LockPatternUtils;
39 import com.android.internal.widget.LockscreenCredential;
40 import com.android.settings.R;
41 import com.android.settings.SettingsPreferenceFragment;
42 import com.android.settings.Utils;
43 import com.android.settings.core.SubSettingLauncher;
44 import com.android.settings.overlay.FeatureFactory;
45 import com.android.settings.password.ChooseLockGeneric;
46 import com.android.settings.password.ChooseLockSettingsHelper;
47 import com.android.settings.privatespace.PrivateProfileContextHelperActivity;
48 import com.android.settings.privatespace.PrivateSpaceMaintainer;
49 import com.android.settingslib.core.AbstractPreferenceController;
50 import com.android.settingslib.transition.SettingsTransitionHelper;
51 import com.android.settingslib.widget.MainSwitchPreference;
52 
53 /** Represents the preference controller for using the same lock as the screen lock */
54 public class UseOneLockControllerSwitch extends AbstractPreferenceController
55         implements Preference.OnPreferenceChangeListener {
56     private static final String TAG = "UseOneLockSwitch";
57     private static final String KEY_UNIFICATION = "private_lock_unification";
58     private final String mPreferenceKey;
59     private final SettingsPreferenceFragment mHost;
60     private final LockPatternUtils mLockPatternUtils;
61     private final UserManager mUserManager;
62     private final int mProfileUserId;
63     private final UserHandle mUserHandle;
64     private LockscreenCredential mCurrentDevicePassword;
65     private LockscreenCredential mCurrentProfilePassword;
66     private MainSwitchPreference mUnifyProfile;
67 
68     @Override
displayPreference(PreferenceScreen screen)69     public void displayPreference(PreferenceScreen screen) {
70         super.displayPreference(screen);
71         mUnifyProfile = screen.findPreference(mPreferenceKey);
72     }
UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host)73     public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host) {
74         this(context, host, KEY_UNIFICATION);
75     }
76 
UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host, String key)77     public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host,
78               String key) {
79         super(context);
80         mHost = host;
81         mUserManager = context.getSystemService(UserManager.class);
82         mLockPatternUtils = FeatureFactory.getFeatureFactory().getSecurityFeatureProvider()
83                   .getLockPatternUtils(context);
84         mUserHandle =  PrivateSpaceMaintainer.getInstance(context).getPrivateProfileHandle();
85         mProfileUserId = mUserHandle != null ? mUserHandle.getIdentifier() : -1;
86         mCurrentDevicePassword = LockscreenCredential.createNone();
87         mCurrentProfilePassword = LockscreenCredential.createNone();
88         this.mPreferenceKey = key;
89     }
90 
91     @Override
getPreferenceKey()92     public String getPreferenceKey() {
93         return mPreferenceKey;
94     }
95 
96     @Override
isAvailable()97     public boolean isAvailable() {
98         return android.os.Flags.allowPrivateProfile()
99                 && android.multiuser.Flags.enablePrivateSpaceFeatures();
100     }
101 
102     @Override
onPreferenceChange(Preference preference, Object value)103     public boolean onPreferenceChange(Preference preference, Object value) {
104         //Checks if the profile is in quiet mode and show a dialog to unpause the profile.
105         if (Utils.startQuietModeDialogIfNecessary(mContext, mUserManager, mProfileUserId)) {
106             return false;
107         }
108         final boolean useOneLock = (Boolean) value;
109         if (useOneLock) {
110             startUnification();
111         } else {
112             showAlertDialog();
113         }
114         return true;
115     }
116 
117     @Override
updateState(Preference preference)118     public void updateState(Preference preference) {
119         if (mUnifyProfile != null) {
120             final boolean separate =
121                       mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId);
122             mUnifyProfile.setChecked(!separate);
123         }
124     }
125 
126     /** Method to handle onActivityResult */
handleActivityResult(int requestCode, int resultCode, @Nullable Intent data)127     public boolean handleActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
128         if (requestCode == UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST
129                   && resultCode == Activity.RESULT_OK && data != null) {
130             mCurrentDevicePassword =
131                       data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
132             separateLocks();
133             return true;
134         } else if (requestCode == UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST
135                   && resultCode == Activity.RESULT_OK && data != null) {
136             mCurrentProfilePassword =
137                       data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
138             unifyLocks();
139             return true;
140         }
141         return false;
142     }
143 
separateLocks()144     private void separateLocks() {
145         final Bundle extras = new Bundle();
146         extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId);
147         extras.putParcelable(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, mCurrentDevicePassword);
148         new SubSettingLauncher(mContext)
149                   .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
150                   .setSourceMetricsCategory(mHost.getMetricsCategory())
151                   .setArguments(extras)
152                   .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
153                   .launch();
154     }
155 
156     /** Unify primary and profile locks. */
startUnification()157     public void startUnification() {
158         // Confirm profile lock
159         final ChooseLockSettingsHelper.Builder builder =
160                   new ChooseLockSettingsHelper.Builder(mHost.getActivity(), mHost);
161         final boolean launched = builder.setRequestCode(UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST)
162                   .setReturnCredentials(true)
163                   .setUserId(mProfileUserId)
164                   .show();
165         if (!launched) {
166             // If profile has no lock, go straight to unification.
167             unifyLocks();
168         }
169     }
170 
unifyLocks()171     private void unifyLocks() {
172         unifyKeepingDeviceLock();
173         if (mCurrentDevicePassword != null) {
174             mCurrentDevicePassword.zeroize();
175             mCurrentDevicePassword = null;
176         }
177         if (mCurrentProfilePassword != null) {
178             mCurrentProfilePassword.zeroize();
179             mCurrentProfilePassword = null;
180         }
181     }
182 
unifyKeepingDeviceLock()183     private void unifyKeepingDeviceLock() {
184         mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false,
185                   mCurrentProfilePassword);
186     }
187 
showAlertDialog()188     private void showAlertDialog() {
189         if (mUserHandle == null) {
190             Log.e(TAG, "Private profile user handle is not expected to be null");
191             mUnifyProfile.setChecked(true);
192             return;
193         }
194         new AlertDialog.Builder(mContext)
195                   .setTitle(R.string.private_space_new_lock_title)
196                   .setMessage(R.string.private_space_new_lock_message)
197                   .setPositiveButton(
198                             R.string.private_space_set_lock_label,
199                             (dialog, which) -> {
200                                 Intent intent = new Intent(mContext,
201                                           PrivateProfileContextHelperActivity.class);
202                                 intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION);
203                                 ((Activity) mContext).startActivityForResultAsUser(intent,
204                                           UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST,
205                                           /*Options*/ null, mUserHandle);
206                             })
207                   .setNegativeButton(R.string.private_space_cancel_label,
208                             (DialogInterface dialog, int which) -> {
209                                 mUnifyProfile.setChecked(true);
210                                 dialog.dismiss();
211                             })
212                   .setOnCancelListener(
213                             (DialogInterface dialog) -> {
214                                 mUnifyProfile.setChecked(true);
215                                 dialog.dismiss();
216                             })
217                   .show();
218     }
219 }
220