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