1 /* 2 * Copyright (C) 2016 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.Fragment; 24 import android.app.tvsettings.TvSettingsEnums; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.pm.UserInfo; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.graphics.drawable.Drawable; 33 import android.os.AsyncTask; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 import androidx.annotation.DrawableRes; 43 import androidx.annotation.IntDef; 44 import androidx.annotation.Keep; 45 import androidx.leanback.preference.LeanbackSettingsFragment; 46 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 47 import androidx.preference.Preference; 48 import androidx.preference.PreferenceGroup; 49 50 import com.android.internal.logging.nano.MetricsProto; 51 import com.android.tv.settings.R; 52 import com.android.tv.settings.SettingsPreferenceFragment; 53 import com.android.tv.settings.dialog.PinDialogFragment; 54 import com.android.tv.settings.users.AppRestrictionsFragment; 55 import com.android.tv.settings.users.RestrictedProfileModel; 56 import com.android.tv.settings.users.RestrictedProfilePinDialogFragment; 57 import com.android.tv.settings.users.RestrictedProfilePinStorage; 58 import com.android.tv.settings.users.UserSwitchListenerService; 59 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 60 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 64 /** 65 * The security settings screen in Tv settings. 66 */ 67 @Keep 68 public class SecurityFragment extends SettingsPreferenceFragment 69 implements PinDialogFragment.ResultListener { 70 71 private static final String TAG = "SecurityFragment"; 72 73 private static final String KEY_UNKNOWN_SOURCES = "unknown_sources"; 74 private static final String KEY_RESTRICTED_PROFILE_GROUP = "restricted_profile_group"; 75 private static final String KEY_RESTRICTED_PROFILE_ENTER = "restricted_profile_enter"; 76 private static final String KEY_RESTRICTED_PROFILE_EXIT = "restricted_profile_exit"; 77 private static final String KEY_RESTRICTED_PROFILE_APPS = "restricted_profile_apps"; 78 private static final String KEY_RESTRICTED_PROFILE_PIN = "restricted_profile_pin"; 79 private static final String KEY_RESTRICTED_PROFILE_CREATE = "restricted_profile_create"; 80 private static final String KEY_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete"; 81 82 private static final String ACTION_RESTRICTED_PROFILE_CREATED = 83 "SecurityFragment.RESTRICTED_PROFILE_CREATED"; 84 private static final String EXTRA_RESTRICTED_PROFILE_INFO = 85 "SecurityFragment.RESTRICTED_PROFILE_INFO"; 86 private static final String SAVESTATE_CREATING_RESTRICTED_PROFILE = 87 "SecurityFragment.CREATING_RESTRICTED_PROFILE"; 88 89 @Retention(RetentionPolicy.SOURCE) 90 @IntDef({PIN_MODE_CHOOSE_LOCKSCREEN, 91 PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT, 92 PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD, 93 PIN_MODE_RESTRICTED_PROFILE_DELETE}) 94 private @interface PinMode {} 95 private static final int PIN_MODE_CHOOSE_LOCKSCREEN = 1; 96 private static final int PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT = 2; 97 private static final int PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD = 3; 98 private static final int PIN_MODE_RESTRICTED_PROFILE_DELETE = 4; 99 100 private Preference mUnknownSourcesPref; 101 private PreferenceGroup mRestrictedProfileGroup; 102 private Preference mRestrictedProfileEnterPref; 103 private Preference mRestrictedProfileExitPref; 104 private Preference mRestrictedProfileAppsPref; 105 private Preference mRestrictedProfilePinPref; 106 private Preference mRestrictedProfileCreatePref; 107 private Preference mRestrictedProfileDeletePref; 108 109 private RestrictedProfileModel mRestrictedProfile; 110 111 private boolean mCreatingRestrictedProfile; 112 private RestrictedProfilePinStorage mRestrictedProfilePinStorage; 113 114 @SuppressLint("StaticFieldLeak") 115 private static CreateRestrictedProfileTask sCreateRestrictedProfileTask; 116 private final BroadcastReceiver mRestrictedProfileReceiver = new BroadcastReceiver() { 117 @Override 118 public void onReceive(Context context, Intent intent) { 119 UserInfo result = intent.getParcelableExtra(EXTRA_RESTRICTED_PROFILE_INFO); 120 if (isResumed()) { 121 onRestrictedUserCreated(result); 122 } 123 } 124 }; 125 126 private Handler mUiThreadHandler; 127 private HandlerThread mBackgroundHandlerThread; 128 private Handler mBackgroundHandler; 129 newInstance()130 public static SecurityFragment newInstance() { 131 return new SecurityFragment(); 132 } 133 134 @Override onCreate(Bundle savedInstanceState)135 public void onCreate(Bundle savedInstanceState) { 136 mRestrictedProfile = new RestrictedProfileModel(getContext()); 137 138 super.onCreate(savedInstanceState); 139 mCreatingRestrictedProfile = savedInstanceState != null 140 && savedInstanceState.getBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE); 141 142 mUiThreadHandler = new Handler(); 143 mBackgroundHandlerThread = new HandlerThread("SecurityFragmentBackgroundThread"); 144 mBackgroundHandlerThread.start(); 145 mBackgroundHandler = new Handler(mBackgroundHandlerThread.getLooper()); 146 } 147 148 @Override onDestroy()149 public void onDestroy() { 150 mBackgroundHandler = null; 151 mBackgroundHandlerThread.quitSafely(); 152 mBackgroundHandlerThread = null; 153 mUiThreadHandler = null; 154 155 super.onDestroy(); 156 157 mRestrictedProfile = null; 158 } 159 160 @Override onResume()161 public void onResume() { 162 super.onResume(); 163 refresh(); 164 LocalBroadcastManager.getInstance(getActivity()) 165 .registerReceiver(mRestrictedProfileReceiver, 166 new IntentFilter(ACTION_RESTRICTED_PROFILE_CREATED)); 167 if (mCreatingRestrictedProfile) { 168 UserInfo userInfo = mRestrictedProfile.getUser(); 169 if (userInfo != null) { 170 onRestrictedUserCreated(userInfo); 171 } 172 } 173 } 174 175 @Override onPause()176 public void onPause() { 177 super.onPause(); 178 LocalBroadcastManager.getInstance(getActivity()) 179 .unregisterReceiver(mRestrictedProfileReceiver); 180 } 181 182 @Override onAttach(Context context)183 public void onAttach(Context context) { 184 super.onAttach(context); 185 mRestrictedProfilePinStorage = RestrictedProfilePinStorage.newInstance(getContext()); 186 mRestrictedProfilePinStorage.bind(); 187 } 188 189 @Override onDetach()190 public void onDetach() { 191 mRestrictedProfilePinStorage.unbind(); 192 mRestrictedProfilePinStorage = null; 193 super.onDetach(); 194 } 195 196 @Override onSaveInstanceState(Bundle outState)197 public void onSaveInstanceState(Bundle outState) { 198 super.onSaveInstanceState(outState); 199 outState.putBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE, mCreatingRestrictedProfile); 200 } 201 202 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)203 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 204 setPreferencesFromResource(R.xml.security, null); 205 206 mUnknownSourcesPref = findPreference(KEY_UNKNOWN_SOURCES); 207 mRestrictedProfileGroup = (PreferenceGroup) findPreference(KEY_RESTRICTED_PROFILE_GROUP); 208 mRestrictedProfileEnterPref = findPreference(KEY_RESTRICTED_PROFILE_ENTER); 209 mRestrictedProfileExitPref = findPreference(KEY_RESTRICTED_PROFILE_EXIT); 210 mRestrictedProfileAppsPref = findPreference(KEY_RESTRICTED_PROFILE_APPS); 211 mRestrictedProfilePinPref = findPreference(KEY_RESTRICTED_PROFILE_PIN); 212 mRestrictedProfileCreatePref = findPreference(KEY_RESTRICTED_PROFILE_CREATE); 213 mRestrictedProfileDeletePref = findPreference(KEY_RESTRICTED_PROFILE_DELETE); 214 } 215 refresh()216 private void refresh() { 217 if (mRestrictedProfile.isCurrentUser()) { 218 // We are in restricted profile 219 mUnknownSourcesPref.setVisible(false); 220 221 mRestrictedProfileGroup.setVisible(true); 222 mRestrictedProfileEnterPref.setVisible(false); 223 mRestrictedProfileExitPref.setVisible(true); 224 mRestrictedProfileAppsPref.setVisible(false); 225 mRestrictedProfilePinPref.setVisible(false); 226 mRestrictedProfileCreatePref.setVisible(false); 227 mRestrictedProfileDeletePref.setVisible(false); 228 } else if (mRestrictedProfile.getUser() != null) { 229 // Not in restricted profile, but it exists 230 mUnknownSourcesPref.setVisible(true); 231 232 mRestrictedProfileGroup.setVisible(true); 233 mRestrictedProfileEnterPref.setVisible(true); 234 mRestrictedProfileExitPref.setVisible(false); 235 mRestrictedProfileAppsPref.setVisible(true); 236 mRestrictedProfilePinPref.setVisible(true); 237 mRestrictedProfileCreatePref.setVisible(false); 238 mRestrictedProfileDeletePref.setVisible(true); 239 240 AppRestrictionsFragment.prepareArgs(mRestrictedProfileAppsPref.getExtras(), 241 mRestrictedProfile.getUser().id, false); 242 } else if (UserManager.supportsMultipleUsers()) { 243 // Not in restricted profile, and it doesn't exist 244 mUnknownSourcesPref.setVisible(true); 245 246 mRestrictedProfileGroup.setVisible(true); 247 mRestrictedProfileEnterPref.setVisible(false); 248 mRestrictedProfileExitPref.setVisible(false); 249 mRestrictedProfileAppsPref.setVisible(false); 250 mRestrictedProfilePinPref.setVisible(false); 251 mRestrictedProfileCreatePref.setVisible(true); 252 mRestrictedProfileDeletePref.setVisible(false); 253 } else { 254 // Not in restricted profile, and can't create one either 255 mUnknownSourcesPref.setVisible(true); 256 257 mRestrictedProfileGroup.setVisible(false); 258 mRestrictedProfileEnterPref.setVisible(false); 259 mRestrictedProfileExitPref.setVisible(false); 260 mRestrictedProfileAppsPref.setVisible(false); 261 mRestrictedProfilePinPref.setVisible(false); 262 mRestrictedProfileCreatePref.setVisible(false); 263 mRestrictedProfileDeletePref.setVisible(false); 264 } 265 266 mRestrictedProfileCreatePref.setEnabled(sCreateRestrictedProfileTask == null); 267 268 mUnknownSourcesPref.setEnabled(!isUnknownSourcesBlocked()); 269 } 270 271 @Override onPreferenceTreeClick(Preference preference)272 public boolean onPreferenceTreeClick(Preference preference) { 273 final String key = preference.getKey(); 274 if (TextUtils.isEmpty(key)) { 275 return super.onPreferenceTreeClick(preference); 276 } 277 switch (key) { 278 case KEY_RESTRICTED_PROFILE_ENTER: 279 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_ENTER_PROFILE); 280 if (mRestrictedProfile.enterUser()) { 281 getActivity().finish(); 282 } 283 return true; 284 case KEY_RESTRICTED_PROFILE_EXIT: 285 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_EXIT_PROFILE); 286 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT); 287 return true; 288 case KEY_RESTRICTED_PROFILE_PIN: 289 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_PROFILE_CHANGE_PIN); 290 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD); 291 return true; 292 case KEY_RESTRICTED_PROFILE_CREATE: 293 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_CREATE_PROFILE); 294 createRestrictedProfile(); 295 return true; 296 case KEY_RESTRICTED_PROFILE_DELETE: 297 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_DELETE_PROFILE); 298 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_DELETE); 299 return true; 300 } 301 return super.onPreferenceTreeClick(preference); 302 } 303 createRestrictedProfile()304 private void createRestrictedProfile() { 305 mBackgroundHandler.post(() -> { 306 boolean pinIsSet = mRestrictedProfilePinStorage.isPinSet(); 307 308 mUiThreadHandler.post(() -> { 309 if (pinIsSet) { 310 addRestrictedUser(); 311 } else { 312 launchPinDialog(PIN_MODE_CHOOSE_LOCKSCREEN); 313 } 314 }); 315 }); 316 } 317 isUnknownSourcesBlocked()318 private boolean isUnknownSourcesBlocked() { 319 final UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE); 320 return um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); 321 } 322 launchPinDialog(@inMode int pinMode)323 private void launchPinDialog(@PinMode int pinMode) { 324 @PinDialogFragment.PinDialogType 325 int pinDialogMode; 326 327 switch (pinMode) { 328 case PIN_MODE_CHOOSE_LOCKSCREEN: 329 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 330 break; 331 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 332 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN; 333 break; 334 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 335 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 336 break; 337 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 338 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_DELETE_PIN; 339 break; 340 default: 341 throw new IllegalArgumentException("Unknown pin mode: " + pinMode); 342 } 343 344 RestrictedProfilePinDialogFragment restrictedProfilePinDialogFragment = 345 RestrictedProfilePinDialogFragment.newInstance(pinDialogMode); 346 restrictedProfilePinDialogFragment.setTargetFragment(this, pinMode); 347 restrictedProfilePinDialogFragment.show(getFragmentManager(), 348 PinDialogFragment.DIALOG_TAG); 349 } 350 351 @Override pinFragmentDone(int requestCode, boolean success)352 public void pinFragmentDone(int requestCode, boolean success) { 353 if (!success) { 354 Log.d(TAG, "Request " + requestCode + " unsuccessful."); 355 return; 356 } 357 358 switch (requestCode) { 359 case PIN_MODE_CHOOSE_LOCKSCREEN: 360 addRestrictedUser(); 361 break; 362 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 363 mRestrictedProfile.exitUser(); 364 getActivity().finish(); 365 break; 366 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 367 // do nothing 368 break; 369 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 370 mUiThreadHandler.post(() -> { 371 mRestrictedProfile.removeUser(); 372 UserSwitchListenerService.updateLaunchPoint(getActivity(), false); 373 refresh(); 374 }); 375 break; 376 default: 377 Log.d(TAG, "Pin request code not recognised: " + requestCode); 378 } 379 } 380 addRestrictedUser()381 private void addRestrictedUser() { 382 if (sCreateRestrictedProfileTask == null) { 383 sCreateRestrictedProfileTask = new CreateRestrictedProfileTask(getContext()); 384 sCreateRestrictedProfileTask.execute(); 385 mCreatingRestrictedProfile = true; 386 } 387 refresh(); 388 } 389 390 /** 391 * Called by other Fragments to decide whether to show or hide profile-related views. 392 */ isRestrictedProfileInEffect(Context context)393 public static boolean isRestrictedProfileInEffect(Context context) { 394 return new RestrictedProfileModel(context).isCurrentUser(); 395 } 396 onRestrictedUserCreated(UserInfo result)397 private void onRestrictedUserCreated(UserInfo result) { 398 int userId = result.id; 399 if (result.isRestricted() 400 && result.restrictedProfileParentId == UserHandle.myUserId()) { 401 final AppRestrictionsFragment restrictionsFragment = 402 AppRestrictionsFragment.newInstance(userId, true); 403 final Fragment settingsFragment = getCallbackFragment(); 404 if (settingsFragment instanceof LeanbackSettingsFragment) { 405 ((LeanbackSettingsFragment) settingsFragment) 406 .startPreferenceFragment(restrictionsFragment); 407 } else if (settingsFragment instanceof TwoPanelSettingsFragment) { 408 ((TwoPanelSettingsFragment) settingsFragment) 409 .startPreferenceFragment(restrictionsFragment); 410 } else { 411 throw new IllegalStateException("Didn't find fragment of expected type: " 412 + settingsFragment); 413 } 414 } 415 mCreatingRestrictedProfile = false; 416 refresh(); 417 } 418 419 private static class CreateRestrictedProfileTask extends AsyncTask<Void, Void, UserInfo> { 420 private final Context mContext; 421 private final UserManager mUserManager; 422 CreateRestrictedProfileTask(Context context)423 CreateRestrictedProfileTask(Context context) { 424 mContext = context.getApplicationContext(); 425 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 426 } 427 428 @Override doInBackground(Void... params)429 protected UserInfo doInBackground(Void... params) { 430 UserInfo restrictedUserInfo = mUserManager.createProfileForUser( 431 mContext.getString(R.string.user_new_profile_name), 432 UserManager.USER_TYPE_FULL_RESTRICTED, /* flags */ 0, UserHandle.myUserId()); 433 if (restrictedUserInfo == null) { 434 final UserInfo existingUserInfo = new RestrictedProfileModel(mContext).getUser(); 435 if (existingUserInfo == null) { 436 Log.wtf(TAG, "Got back a null user handle!"); 437 } 438 return existingUserInfo; 439 } 440 int userId = restrictedUserInfo.id; 441 UserHandle user = new UserHandle(userId); 442 mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); 443 Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default); 444 mUserManager.setUserIcon(userId, bitmap); 445 // Add shared accounts 446 AccountManager.get(mContext).addSharedAccountsFromParentUser( 447 UserHandle.of(UserHandle.myUserId()), user); 448 return restrictedUserInfo; 449 } 450 451 @Override onPostExecute(UserInfo result)452 protected void onPostExecute(UserInfo result) { 453 sCreateRestrictedProfileTask = null; 454 if (result == null) { 455 return; 456 } 457 UserSwitchListenerService.updateLaunchPoint(mContext, true); 458 LocalBroadcastManager.getInstance(mContext).sendBroadcast( 459 new Intent(ACTION_RESTRICTED_PROFILE_CREATED) 460 .putExtra(EXTRA_RESTRICTED_PROFILE_INFO, result)); 461 } 462 createBitmapFromDrawable(@rawableRes int resId)463 private Bitmap createBitmapFromDrawable(@DrawableRes int resId) { 464 Drawable icon = mContext.getDrawable(resId); 465 if (icon == null) { 466 throw new IllegalArgumentException("Drawable is missing!"); 467 } 468 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 469 Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), 470 Bitmap.Config.ARGB_8888); 471 icon.draw(new Canvas(bitmap)); 472 return bitmap; 473 } 474 } 475 476 @Override getMetricsCategory()477 public int getMetricsCategory() { 478 return MetricsProto.MetricsEvent.SECURITY; 479 } 480 481 @Override getPageId()482 protected int getPageId() { 483 return TvSettingsEnums.APPS_SECURITY_RESTRICTIONS; 484 } 485 } 486