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.tv.settings.system; 18 19 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected; 20 21 import android.accounts.AccountManager; 22 import android.annotation.SuppressLint; 23 import android.app.admin.DevicePolicyManager; 24 import android.app.tvsettings.TvSettingsEnums; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.UserInfo; 31 import android.graphics.Bitmap; 32 import android.graphics.Canvas; 33 import android.graphics.drawable.Drawable; 34 import android.os.AsyncTask; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.text.TextUtils; 41 import android.util.Log; 42 43 import androidx.annotation.DrawableRes; 44 import androidx.annotation.IntDef; 45 import androidx.annotation.Keep; 46 import androidx.fragment.app.Fragment; 47 import androidx.leanback.preference.LeanbackSettingsFragmentCompat; 48 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 49 import androidx.preference.Preference; 50 import androidx.preference.PreferenceGroup; 51 52 import com.android.tv.settings.R; 53 import com.android.tv.settings.SettingsPreferenceFragment; 54 import com.android.tv.settings.dialog.PinDialogFragment; 55 import com.android.tv.settings.users.AppRestrictionsFragment; 56 import com.android.tv.settings.users.RestrictedProfileModel; 57 import com.android.tv.settings.users.RestrictedProfilePinDialogFragment; 58 import com.android.tv.settings.users.RestrictedProfilePinStorage; 59 import com.android.tv.settings.users.UserSwitchListenerService; 60 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 61 62 import java.lang.annotation.Retention; 63 import java.lang.annotation.RetentionPolicy; 64 import java.util.List; 65 66 /** 67 * Base fragment for security settings. 68 */ 69 abstract class BaseSecurityFragment extends SettingsPreferenceFragment 70 implements PinDialogFragment.ResultListener { 71 72 private static final String TAG = "BaseSecurityFragment"; 73 74 protected static final String KEY_UNKNOWN_SOURCES = "unknown_sources"; 75 protected static final String KEY_RESTRICTED_PROFILE_GROUP = "restricted_profile_group"; 76 protected static final String KEY_RESTRICTED_PROFILE_ENTER = "restricted_profile_enter"; 77 protected static final String KEY_RESTRICTED_PROFILE_EXIT = "restricted_profile_exit"; 78 protected static final String KEY_RESTRICTED_PROFILE_APPS = "restricted_profile_apps"; 79 protected static final String KEY_RESTRICTED_PROFILE_PIN = "restricted_profile_pin"; 80 protected static final String KEY_RESTRICTED_PROFILE_CREATE = "restricted_profile_create"; 81 protected static final String KEY_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete"; 82 protected static final String KEY_RESTRICTED_PROFILE_SKIP = "restricted_profile_skip"; 83 protected static final String KEY_MANAGE_DEVICE_ADMIN = "manage_device_admin"; 84 protected static final String KEY_ENTERPRISE_PRIVACY = "enterprise_privacy"; 85 86 private static final String ACTION_RESTRICTED_PROFILE_CREATED = 87 "SecurityFragment.RESTRICTED_PROFILE_CREATED"; 88 private static final String EXTRA_RESTRICTED_PROFILE_INFO = 89 "SecurityFragment.RESTRICTED_PROFILE_INFO"; 90 private static final String SAVESTATE_CREATING_RESTRICTED_PROFILE = 91 "SecurityFragment.CREATING_RESTRICTED_PROFILE"; 92 93 @Retention(RetentionPolicy.SOURCE) 94 @IntDef({PIN_MODE_CHOOSE_LOCKSCREEN, 95 PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT, 96 PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD, 97 PIN_MODE_RESTRICTED_PROFILE_DELETE}) 98 private @interface PinMode {} 99 private static final int PIN_MODE_CHOOSE_LOCKSCREEN = 1; 100 private static final int PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT = 2; 101 private static final int PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD = 3; 102 private static final int PIN_MODE_RESTRICTED_PROFILE_DELETE = 4; 103 104 105 protected RestrictedProfileModel mRestrictedProfile; 106 107 private boolean mCreatingRestrictedProfile; 108 private RestrictedProfilePinStorage mRestrictedProfilePinStorage; 109 110 @SuppressLint("StaticFieldLeak") 111 private static CreateRestrictedProfileTask sCreateRestrictedProfileTask; 112 private final BroadcastReceiver mRestrictedProfileReceiver = new BroadcastReceiver() { 113 @Override 114 public void onReceive(Context context, Intent intent) { 115 UserInfo result = intent.getParcelableExtra(EXTRA_RESTRICTED_PROFILE_INFO); 116 if (isResumed()) { 117 onRestrictedUserCreated(result); 118 } 119 } 120 }; 121 122 private Handler mUiThreadHandler; 123 private HandlerThread mBackgroundHandlerThread; 124 private Handler mBackgroundHandler; 125 126 @Override onCreate(Bundle savedInstanceState)127 public void onCreate(Bundle savedInstanceState) { 128 mRestrictedProfile = new RestrictedProfileModel(getContext()); 129 130 super.onCreate(savedInstanceState); 131 mCreatingRestrictedProfile = savedInstanceState != null 132 && savedInstanceState.getBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE); 133 134 mUiThreadHandler = new Handler(); 135 mBackgroundHandlerThread = new HandlerThread("SecurityFragmentBackgroundThread"); 136 mBackgroundHandlerThread.start(); 137 mBackgroundHandler = new Handler(mBackgroundHandlerThread.getLooper()); 138 } 139 140 @Override onDestroy()141 public void onDestroy() { 142 mBackgroundHandler = null; 143 mBackgroundHandlerThread.quitSafely(); 144 mBackgroundHandlerThread = null; 145 mUiThreadHandler = null; 146 147 super.onDestroy(); 148 149 mRestrictedProfile = null; 150 } 151 152 @Override onResume()153 public void onResume() { 154 super.onResume(); 155 refresh(); 156 LocalBroadcastManager.getInstance(getActivity()) 157 .registerReceiver(mRestrictedProfileReceiver, 158 new IntentFilter(ACTION_RESTRICTED_PROFILE_CREATED)); 159 if (mCreatingRestrictedProfile) { 160 UserInfo userInfo = mRestrictedProfile.getUser(); 161 if (userInfo != null) { 162 onRestrictedUserCreated(userInfo); 163 } 164 } 165 } 166 167 @Override onPause()168 public void onPause() { 169 super.onPause(); 170 LocalBroadcastManager.getInstance(getActivity()) 171 .unregisterReceiver(mRestrictedProfileReceiver); 172 } 173 174 @Override onAttach(Context context)175 public void onAttach(Context context) { 176 super.onAttach(context); 177 mRestrictedProfilePinStorage = RestrictedProfilePinStorage.newInstance(getContext()); 178 mRestrictedProfilePinStorage.bind(); 179 } 180 181 @Override onDetach()182 public void onDetach() { 183 mRestrictedProfilePinStorage.unbind(); 184 mRestrictedProfilePinStorage = null; 185 super.onDetach(); 186 } 187 188 @Override onSaveInstanceState(Bundle outState)189 public void onSaveInstanceState(Bundle outState) { 190 super.onSaveInstanceState(outState); 191 outState.putBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE, mCreatingRestrictedProfile); 192 } 193 194 refresh()195 abstract protected void refresh(); 196 197 @Override onPreferenceTreeClick(Preference preference)198 public boolean onPreferenceTreeClick(Preference preference) { 199 final String key = preference.getKey(); 200 if (TextUtils.isEmpty(key)) { 201 return super.onPreferenceTreeClick(preference); 202 } 203 switch (key) { 204 case KEY_RESTRICTED_PROFILE_ENTER: 205 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_ENTER_PROFILE); 206 if (mRestrictedProfile.enterUser()) { 207 getActivity().finish(); 208 } 209 return true; 210 case KEY_RESTRICTED_PROFILE_EXIT: 211 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_EXIT_PROFILE); 212 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT); 213 return true; 214 case KEY_RESTRICTED_PROFILE_PIN: 215 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_PROFILE_CHANGE_PIN); 216 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD); 217 return true; 218 case KEY_RESTRICTED_PROFILE_CREATE: 219 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_CREATE_PROFILE); 220 createRestrictedProfile(); 221 return true; 222 case KEY_RESTRICTED_PROFILE_DELETE: 223 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_DELETE_PROFILE); 224 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_DELETE); 225 return true; 226 } 227 return super.onPreferenceTreeClick(preference); 228 } 229 createRestrictedProfile()230 private void createRestrictedProfile() { 231 mBackgroundHandler.post(() -> { 232 boolean pinIsSet = mRestrictedProfilePinStorage.isPinSet(); 233 234 mUiThreadHandler.post(() -> { 235 if (pinIsSet) { 236 addRestrictedUser(); 237 } else { 238 launchPinDialog(PIN_MODE_CHOOSE_LOCKSCREEN); 239 } 240 }); 241 }); 242 } 243 launchPinDialog(@inMode int pinMode)244 private void launchPinDialog(@PinMode int pinMode) { 245 @PinDialogFragment.PinDialogType 246 int pinDialogMode; 247 248 switch (pinMode) { 249 case PIN_MODE_CHOOSE_LOCKSCREEN: 250 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 251 break; 252 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 253 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN; 254 break; 255 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 256 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 257 break; 258 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 259 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_DELETE_PIN; 260 break; 261 default: 262 throw new IllegalArgumentException("Unknown pin mode: " + pinMode); 263 } 264 265 RestrictedProfilePinDialogFragment restrictedProfilePinDialogFragment = 266 RestrictedProfilePinDialogFragment.newInstance(pinDialogMode); 267 restrictedProfilePinDialogFragment.setTargetFragment(this, pinMode); 268 restrictedProfilePinDialogFragment.show(getFragmentManager(), 269 PinDialogFragment.DIALOG_TAG); 270 } 271 272 @Override pinFragmentDone(int requestCode, boolean success)273 public void pinFragmentDone(int requestCode, boolean success) { 274 if (!success) { 275 Log.d(TAG, "Request " + requestCode + " unsuccessful."); 276 return; 277 } 278 279 switch (requestCode) { 280 case PIN_MODE_CHOOSE_LOCKSCREEN: 281 addRestrictedUser(); 282 break; 283 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 284 mRestrictedProfile.exitUser(); 285 mUiThreadHandler.post(() -> getActivity().finish()); 286 break; 287 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 288 // do nothing 289 break; 290 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 291 mUiThreadHandler.post(() -> { 292 mRestrictedProfile.removeUser(); 293 UserSwitchListenerService.onUserCreatedOrDeleted(getActivity()); 294 refresh(); 295 }); 296 break; 297 default: 298 Log.d(TAG, "Pin request code not recognised: " + requestCode); 299 } 300 } 301 addRestrictedUser()302 private void addRestrictedUser() { 303 if (sCreateRestrictedProfileTask == null) { 304 sCreateRestrictedProfileTask = new CreateRestrictedProfileTask(getContext()); 305 sCreateRestrictedProfileTask.execute(); 306 mCreatingRestrictedProfile = true; 307 } 308 refresh(); 309 } 310 onRestrictedUserCreated(UserInfo result)311 private void onRestrictedUserCreated(UserInfo result) { 312 int userId = result.id; 313 if (result.isRestricted() 314 && result.restrictedProfileParentId == UserHandle.myUserId()) { 315 final AppRestrictionsFragment restrictionsFragment = 316 AppRestrictionsFragment.newInstance(userId, true, 317 shouldExitAfterUpdatingApps()); 318 final Fragment settingsFragment = getCallbackFragment(); 319 if (settingsFragment instanceof LeanbackSettingsFragmentCompat) { 320 ((LeanbackSettingsFragmentCompat) settingsFragment) 321 .startPreferenceFragment(restrictionsFragment); 322 } else if (settingsFragment instanceof TwoPanelSettingsFragment) { 323 ((TwoPanelSettingsFragment) settingsFragment) 324 .startPreferenceFragment(restrictionsFragment); 325 } else { 326 throw new IllegalStateException("Didn't find fragment of expected type: " 327 + settingsFragment); 328 } 329 } 330 mCreatingRestrictedProfile = false; 331 refresh(); 332 } 333 shouldExitAfterUpdatingApps()334 protected boolean shouldExitAfterUpdatingApps() { 335 return false; 336 } 337 338 private static class CreateRestrictedProfileTask extends AsyncTask<Void, Void, UserInfo> { 339 private final Context mContext; 340 private final UserManager mUserManager; 341 CreateRestrictedProfileTask(Context context)342 CreateRestrictedProfileTask(Context context) { 343 mContext = context.getApplicationContext(); 344 mUserManager = mContext.getSystemService(UserManager.class); 345 } 346 347 @Override doInBackground(Void... params)348 protected UserInfo doInBackground(Void... params) { 349 UserInfo restrictedUserInfo = mUserManager.createProfileForUser( 350 mContext.getString(R.string.user_new_profile_name), 351 UserManager.USER_TYPE_FULL_RESTRICTED, /* flags */ 0, UserHandle.myUserId()); 352 if (restrictedUserInfo == null) { 353 final UserInfo existingUserInfo = new RestrictedProfileModel(mContext).getUser(); 354 if (existingUserInfo == null) { 355 Log.wtf(TAG, "Got back a null user handle!"); 356 } 357 return existingUserInfo; 358 } 359 int userId = restrictedUserInfo.id; 360 UserHandle user = new UserHandle(userId); 361 mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); 362 Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default); 363 mUserManager.setUserIcon(userId, bitmap); 364 // Add shared accounts 365 AccountManager.get(mContext).addSharedAccountsFromParentUser( 366 UserHandle.of(UserHandle.myUserId()), user); 367 return restrictedUserInfo; 368 } 369 370 @Override onPostExecute(UserInfo result)371 protected void onPostExecute(UserInfo result) { 372 sCreateRestrictedProfileTask = null; 373 if (result == null) { 374 return; 375 } 376 UserSwitchListenerService.onUserCreatedOrDeleted(mContext); 377 LocalBroadcastManager.getInstance(mContext).sendBroadcast( 378 new Intent(ACTION_RESTRICTED_PROFILE_CREATED) 379 .putExtra(EXTRA_RESTRICTED_PROFILE_INFO, result)); 380 } 381 createBitmapFromDrawable(@rawableRes int resId)382 private Bitmap createBitmapFromDrawable(@DrawableRes int resId) { 383 Drawable icon = mContext.getDrawable(resId); 384 if (icon == null) { 385 throw new IllegalArgumentException("Drawable is missing!"); 386 } 387 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 388 Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), 389 Bitmap.Config.ARGB_8888); 390 icon.draw(new Canvas(bitmap)); 391 return bitmap; 392 } 393 } 394 isRestrictedProfileCreationInProgress()395 protected boolean isRestrictedProfileCreationInProgress() { 396 return sCreateRestrictedProfileTask != null; 397 } 398 399 @Override getPageId()400 protected int getPageId() { 401 return TvSettingsEnums.APPS_SECURITY_RESTRICTIONS; 402 } 403 } 404