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 android.accounts.AccountManager; 20 import android.annotation.SuppressLint; 21 import android.app.ActivityManager; 22 import android.app.Fragment; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 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.RemoteException; 37 import android.os.ServiceManager; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.provider.Settings; 41 import android.support.annotation.DrawableRes; 42 import android.support.annotation.IntDef; 43 import android.support.v17.preference.LeanbackPreferenceFragment; 44 import android.support.v17.preference.LeanbackSettingsFragment; 45 import android.support.v4.content.LocalBroadcastManager; 46 import android.support.v7.preference.Preference; 47 import android.support.v7.preference.PreferenceGroup; 48 import android.support.v7.preference.TwoStatePreference; 49 import android.text.TextUtils; 50 import android.util.Log; 51 52 import com.android.internal.widget.ILockSettings; 53 import com.android.internal.widget.LockPatternUtils; 54 import com.android.internal.widget.VerifyCredentialResponse; 55 import com.android.tv.settings.R; 56 import com.android.tv.settings.dialog.PinDialogFragment; 57 import com.android.tv.settings.users.AppRestrictionsFragment; 58 import com.android.tv.settings.users.RestrictedProfilePinDialogFragment; 59 import com.android.tv.settings.users.UserSwitchListenerService; 60 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.util.List; 64 65 public class SecurityFragment extends LeanbackPreferenceFragment 66 implements RestrictedProfilePinDialogFragment.Callback { 67 68 private static final String TAG = "SecurityFragment"; 69 70 private static final String KEY_UNKNOWN_SOURCES = "unknown_sources"; 71 private static final String KEY_VERIFY_APPS = "verify_apps"; 72 private static final String KEY_RESTRICTED_PROFILE_GROUP = "restricted_profile_group"; 73 private static final String KEY_RESTRICTED_PROFILE_ENTER = "restricted_profile_enter"; 74 private static final String KEY_RESTRICTED_PROFILE_EXIT = "restricted_profile_exit"; 75 private static final String KEY_RESTRICTED_PROFILE_APPS = "restricted_profile_apps"; 76 private static final String KEY_RESTRICTED_PROFILE_PIN = "restricted_profile_pin"; 77 private static final String KEY_RESTRICTED_PROFILE_CREATE = "restricted_profile_create"; 78 private static final String KEY_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete"; 79 80 private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; 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 TwoStatePreference mVerifyAppsPref; 102 private PreferenceGroup mRestrictedProfileGroup; 103 private Preference mRestrictedProfileEnterPref; 104 private Preference mRestrictedProfileExitPref; 105 private Preference mRestrictedProfileAppsPref; 106 private Preference mRestrictedProfilePinPref; 107 private Preference mRestrictedProfileCreatePref; 108 private Preference mRestrictedProfileDeletePref; 109 110 private UserManager mUserManager; 111 private UserInfo mRestrictedUserInfo; 112 private ILockSettings mLockSettingsService; 113 114 private boolean mCreatingRestrictedProfile; 115 @SuppressLint("StaticFieldLeak") 116 private static CreateRestrictedProfileTask sCreateRestrictedProfileTask; 117 private final BroadcastReceiver mRestrictedProfileReceiver = new BroadcastReceiver() { 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 UserInfo result = intent.getParcelableExtra(EXTRA_RESTRICTED_PROFILE_INFO); 121 if (isResumed()) { 122 onRestrictedUserCreated(result); 123 } 124 } 125 }; 126 127 private final Handler mHandler = new Handler(); 128 129 public static SecurityFragment newInstance() { 130 return new SecurityFragment(); 131 } 132 133 @Override 134 public void onCreate(Bundle savedInstanceState) { 135 mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); 136 super.onCreate(savedInstanceState); 137 mCreatingRestrictedProfile = savedInstanceState != null 138 && savedInstanceState.getBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE); 139 } 140 141 @Override 142 public void onResume() { 143 super.onResume(); 144 refresh(); 145 LocalBroadcastManager.getInstance(getActivity()) 146 .registerReceiver(mRestrictedProfileReceiver, 147 new IntentFilter(ACTION_RESTRICTED_PROFILE_CREATED)); 148 if (mCreatingRestrictedProfile) { 149 UserInfo userInfo = findRestrictedUser(mUserManager); 150 if (userInfo != null) { 151 onRestrictedUserCreated(userInfo); 152 } 153 } 154 } 155 156 @Override 157 public void onPause() { 158 super.onPause(); 159 LocalBroadcastManager.getInstance(getActivity()) 160 .unregisterReceiver(mRestrictedProfileReceiver); 161 } 162 163 @Override 164 public void onSaveInstanceState(Bundle outState) { 165 super.onSaveInstanceState(outState); 166 outState.putBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE, mCreatingRestrictedProfile); 167 } 168 169 @Override 170 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 171 setPreferencesFromResource(R.xml.security, null); 172 173 mUnknownSourcesPref = findPreference(KEY_UNKNOWN_SOURCES); 174 mVerifyAppsPref = (TwoStatePreference) findPreference(KEY_VERIFY_APPS); 175 mRestrictedProfileGroup = (PreferenceGroup) findPreference(KEY_RESTRICTED_PROFILE_GROUP); 176 mRestrictedProfileEnterPref = findPreference(KEY_RESTRICTED_PROFILE_ENTER); 177 mRestrictedProfileExitPref = findPreference(KEY_RESTRICTED_PROFILE_EXIT); 178 mRestrictedProfileAppsPref = findPreference(KEY_RESTRICTED_PROFILE_APPS); 179 mRestrictedProfilePinPref = findPreference(KEY_RESTRICTED_PROFILE_PIN); 180 mRestrictedProfileCreatePref = findPreference(KEY_RESTRICTED_PROFILE_CREATE); 181 mRestrictedProfileDeletePref = findPreference(KEY_RESTRICTED_PROFILE_DELETE); 182 } 183 184 private void refresh() { 185 if (isRestrictedProfileInEffect(mUserManager)) { 186 // We are in restricted profile 187 mUnknownSourcesPref.setVisible(false); 188 mVerifyAppsPref.setVisible(false); 189 190 mRestrictedProfileGroup.setVisible(true); 191 mRestrictedProfileEnterPref.setVisible(false); 192 mRestrictedProfileExitPref.setVisible(true); 193 mRestrictedProfileAppsPref.setVisible(false); 194 mRestrictedProfilePinPref.setVisible(false); 195 mRestrictedProfileCreatePref.setVisible(false); 196 mRestrictedProfileDeletePref.setVisible(false); 197 } else if (getRestrictedUser() != null) { 198 // Not in restricted profile, but it exists 199 mUnknownSourcesPref.setVisible(true); 200 mVerifyAppsPref.setVisible(shouldShowVerifierSetting()); 201 202 mRestrictedProfileGroup.setVisible(true); 203 mRestrictedProfileEnterPref.setVisible(true); 204 mRestrictedProfileExitPref.setVisible(false); 205 mRestrictedProfileAppsPref.setVisible(true); 206 mRestrictedProfilePinPref.setVisible(true); 207 mRestrictedProfileCreatePref.setVisible(false); 208 mRestrictedProfileDeletePref.setVisible(true); 209 210 AppRestrictionsFragment.prepareArgs(mRestrictedProfileAppsPref.getExtras(), 211 getRestrictedUser().id, false); 212 } else if (UserManager.supportsMultipleUsers()) { 213 // Not in restricted profile, and it doesn't exist 214 mUnknownSourcesPref.setVisible(true); 215 mVerifyAppsPref.setVisible(shouldShowVerifierSetting()); 216 217 mRestrictedProfileGroup.setVisible(true); 218 mRestrictedProfileEnterPref.setVisible(false); 219 mRestrictedProfileExitPref.setVisible(false); 220 mRestrictedProfileAppsPref.setVisible(false); 221 mRestrictedProfilePinPref.setVisible(false); 222 mRestrictedProfileCreatePref.setVisible(true); 223 mRestrictedProfileDeletePref.setVisible(false); 224 } else { 225 // Not in restricted profile, and can't create one either 226 mUnknownSourcesPref.setVisible(true); 227 mVerifyAppsPref.setVisible(shouldShowVerifierSetting()); 228 229 mRestrictedProfileGroup.setVisible(false); 230 mRestrictedProfileEnterPref.setVisible(false); 231 mRestrictedProfileExitPref.setVisible(false); 232 mRestrictedProfileAppsPref.setVisible(false); 233 mRestrictedProfilePinPref.setVisible(false); 234 mRestrictedProfileCreatePref.setVisible(false); 235 mRestrictedProfileDeletePref.setVisible(false); 236 } 237 238 mRestrictedProfileCreatePref.setEnabled(sCreateRestrictedProfileTask == null); 239 240 mUnknownSourcesPref.setEnabled(!isUnknownSourcesBlocked()); 241 mVerifyAppsPref.setChecked(isVerifyAppsEnabled()); 242 mVerifyAppsPref.setEnabled(isVerifierInstalled()); 243 } 244 245 @Override 246 public boolean onPreferenceTreeClick(Preference preference) { 247 final String key = preference.getKey(); 248 if (TextUtils.isEmpty(key)) { 249 return super.onPreferenceTreeClick(preference); 250 } 251 switch (key) { 252 case KEY_VERIFY_APPS: 253 setVerifyAppsEnabled(mVerifyAppsPref.isChecked()); 254 return true; 255 case KEY_RESTRICTED_PROFILE_ENTER: 256 final UserInfo restrictedUser = getRestrictedUser(); 257 if (restrictedUser == null) { 258 Log.e(TAG, "Tried to enter non-existent restricted user"); 259 return true; 260 } 261 switchUserNow(restrictedUser.id); 262 getActivity().finish(); 263 return true; 264 case KEY_RESTRICTED_PROFILE_EXIT: 265 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT); 266 return true; 267 case KEY_RESTRICTED_PROFILE_PIN: 268 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD); 269 return true; 270 case KEY_RESTRICTED_PROFILE_CREATE: 271 if (hasLockscreenSecurity(new LockPatternUtils(getActivity()))) { 272 addRestrictedUser(); 273 } else { 274 launchPinDialog(PIN_MODE_CHOOSE_LOCKSCREEN); 275 } 276 return true; 277 case KEY_RESTRICTED_PROFILE_DELETE: 278 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_DELETE); 279 return true; 280 } 281 return super.onPreferenceTreeClick(preference); 282 } 283 284 private boolean isUnknownSourcesBlocked() { 285 final UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE); 286 return um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); 287 } 288 289 private boolean isVerifyAppsEnabled() { 290 return Settings.Global.getInt(getContext().getContentResolver(), 291 Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) > 0 && isVerifierInstalled(); 292 } 293 294 private void setVerifyAppsEnabled(boolean enable) { 295 Settings.Global.putInt(getContext().getContentResolver(), 296 Settings.Global.PACKAGE_VERIFIER_ENABLE, enable ? 1 : 0); 297 } 298 299 private boolean isVerifierInstalled() { 300 final PackageManager pm = getContext().getPackageManager(); 301 final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); 302 verification.setType(PACKAGE_MIME_TYPE); 303 verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 304 final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0); 305 return receivers.size() > 0; 306 } 307 308 private boolean shouldShowVerifierSetting() { 309 return Settings.Global.getInt(getContext().getContentResolver(), 310 Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0; 311 } 312 313 private void launchPinDialog(@PinMode int pinMode) { 314 @PinDialogFragment.PinDialogType 315 int pinDialogMode; 316 317 switch (pinMode) { 318 case PIN_MODE_CHOOSE_LOCKSCREEN: 319 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 320 break; 321 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 322 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN; 323 break; 324 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 325 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN; 326 break; 327 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 328 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN; 329 break; 330 default: 331 throw new IllegalArgumentException("Unknown pin mode: " + pinMode); 332 } 333 334 RestrictedProfilePinDialogFragment restrictedProfilePinDialogFragment = 335 RestrictedProfilePinDialogFragment.newInstance(pinDialogMode); 336 restrictedProfilePinDialogFragment.setTargetFragment(this, pinMode); 337 restrictedProfilePinDialogFragment.show(getFragmentManager(), 338 PinDialogFragment.DIALOG_TAG); 339 } 340 341 @Override 342 public void saveLockPassword(String pin, int quality) { 343 new LockPatternUtils(getActivity()).saveLockPassword(pin, null, quality, 344 UserHandle.myUserId()); 345 } 346 347 @Override 348 public boolean checkPassword(String password, int userId) { 349 try { 350 return getLockSettings().checkCredential(password, 351 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, userId, null /* progressCallback */) 352 .getResponseCode() == VerifyCredentialResponse.RESPONSE_OK; 353 } catch (final RemoteException e) { 354 // ignore 355 } 356 return false; 357 } 358 359 @Override 360 public boolean hasLockscreenSecurity() { 361 return hasLockscreenSecurity(new LockPatternUtils(getActivity())); 362 } 363 364 private ILockSettings getLockSettings() { 365 if (mLockSettingsService == null) { 366 mLockSettingsService = ILockSettings.Stub.asInterface( 367 ServiceManager.getService("lock_settings")); 368 } 369 return mLockSettingsService; 370 } 371 372 private static boolean hasLockscreenSecurity(LockPatternUtils lpu) { 373 return lpu.isLockPasswordEnabled(UserHandle.myUserId()) 374 || lpu.isLockPatternEnabled(UserHandle.myUserId()); 375 } 376 377 @Override 378 public void pinFragmentDone(int requestCode, boolean success) { 379 switch (requestCode) { 380 case PIN_MODE_CHOOSE_LOCKSCREEN: 381 if (success) { 382 addRestrictedUser(); 383 } 384 break; 385 case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT: 386 if (success) { 387 UserInfo myUserInfo = 388 UserManager.get(getActivity()).getUserInfo(UserHandle.myUserId()); 389 if (myUserInfo == null || 390 myUserInfo.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) { 391 switchUserNow(UserHandle.USER_SYSTEM); 392 } else { 393 switchUserNow(myUserInfo.restrictedProfileParentId); 394 } 395 getActivity().finish(); 396 } 397 break; 398 case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD: 399 // do nothing 400 break; 401 case PIN_MODE_RESTRICTED_PROFILE_DELETE: 402 if (success) { 403 removeRestrictedUser(); 404 new LockPatternUtils(getActivity()).clearLock(null, UserHandle.myUserId()); 405 } 406 break; 407 } 408 } 409 410 public static UserInfo findRestrictedUser(UserManager userManager) { 411 for (UserInfo userInfo : userManager.getUsers()) { 412 if (userInfo.isRestricted()) { 413 return userInfo; 414 } 415 } 416 return null; 417 } 418 419 private UserInfo getRestrictedUser() { 420 if (mRestrictedUserInfo == null) { 421 mRestrictedUserInfo = findRestrictedUser(mUserManager); 422 } 423 return mRestrictedUserInfo; 424 } 425 426 private static void switchUserNow(int userId) { 427 try { 428 ActivityManager.getService().switchUser(userId); 429 } catch (RemoteException re) { 430 Log.e(TAG, "Caught exception while switching user! ", re); 431 } 432 } 433 434 private void addRestrictedUser() { 435 if (sCreateRestrictedProfileTask == null) { 436 sCreateRestrictedProfileTask = new CreateRestrictedProfileTask(getContext(), 437 mUserManager); 438 sCreateRestrictedProfileTask.execute(); 439 mCreatingRestrictedProfile = true; 440 } 441 refresh(); 442 } 443 444 private void removeRestrictedUser() { 445 final UserInfo restrictedUser = getRestrictedUser(); 446 if (restrictedUser == null) { 447 Log.w(TAG, "No restricted user to remove?"); 448 return; 449 } 450 final int restrictedUserHandle = restrictedUser.id; 451 mRestrictedUserInfo = null; 452 mHandler.post(() -> { 453 mUserManager.removeUser(restrictedUserHandle); 454 UserSwitchListenerService.updateLaunchPoint(getActivity(), false); 455 refresh(); 456 }); 457 } 458 459 public static boolean isRestrictedProfileInEffect(Context context) { 460 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 461 UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId()); 462 return userInfo.isRestricted(); 463 } 464 465 private static boolean isRestrictedProfileInEffect(UserManager userManager) { 466 UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId()); 467 return userInfo.isRestricted(); 468 } 469 470 private void onRestrictedUserCreated(UserInfo result) { 471 int userId = result.id; 472 if (result.isRestricted() 473 && result.restrictedProfileParentId == UserHandle.myUserId()) { 474 final AppRestrictionsFragment restrictionsFragment = 475 AppRestrictionsFragment.newInstance(userId, true); 476 final Fragment settingsFragment = getCallbackFragment(); 477 if (settingsFragment instanceof LeanbackSettingsFragment) { 478 ((LeanbackSettingsFragment) settingsFragment) 479 .startPreferenceFragment(restrictionsFragment); 480 } else { 481 throw new IllegalStateException("Didn't find fragment of expected type: " 482 + settingsFragment); 483 } 484 } 485 mCreatingRestrictedProfile = false; 486 refresh(); 487 } 488 489 private static class CreateRestrictedProfileTask extends AsyncTask<Void, Void, UserInfo> { 490 private final Context mContext; 491 private final UserManager mUserManager; 492 493 CreateRestrictedProfileTask(Context context, UserManager userManager) { 494 mContext = context.getApplicationContext(); 495 mUserManager = userManager; 496 } 497 498 @Override 499 protected UserInfo doInBackground(Void... params) { 500 UserInfo restrictedUserInfo = mUserManager.createProfileForUser( 501 mContext.getString(R.string.user_new_profile_name), 502 UserInfo.FLAG_RESTRICTED, UserHandle.myUserId()); 503 if (restrictedUserInfo == null) { 504 final UserInfo existingUserInfo = findRestrictedUser(mUserManager); 505 if (existingUserInfo == null) { 506 Log.wtf(TAG, "Got back a null user handle!"); 507 } 508 return existingUserInfo; 509 } 510 int userId = restrictedUserInfo.id; 511 UserHandle user = new UserHandle(userId); 512 mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); 513 Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default); 514 mUserManager.setUserIcon(userId, bitmap); 515 // Add shared accounts 516 AccountManager.get(mContext).addSharedAccountsFromParentUser( 517 UserHandle.of(UserHandle.myUserId()), user); 518 return restrictedUserInfo; 519 } 520 521 @Override 522 protected void onPostExecute(UserInfo result) { 523 sCreateRestrictedProfileTask = null; 524 if (result == null) { 525 return; 526 } 527 UserSwitchListenerService.updateLaunchPoint(mContext, true); 528 LocalBroadcastManager.getInstance(mContext).sendBroadcast( 529 new Intent(ACTION_RESTRICTED_PROFILE_CREATED) 530 .putExtra(EXTRA_RESTRICTED_PROFILE_INFO, result)); 531 } 532 533 private Bitmap createBitmapFromDrawable(@DrawableRes int resId) { 534 Drawable icon = mContext.getDrawable(resId); 535 if (icon == null) { 536 throw new IllegalArgumentException("Drawable is missing!"); 537 } 538 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 539 Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), 540 Bitmap.Config.ARGB_8888); 541 icon.draw(new Canvas(bitmap)); 542 return bitmap; 543 } 544 } 545 } 546