1 /* 2 * Copyright (C) 2014 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.users; 18 19 import com.android.tv.settings.R; 20 import com.android.tv.settings.dialog.DialogFragment; 21 import com.android.tv.settings.dialog.DialogFragment.Action; 22 23 import android.accounts.Account; 24 import android.accounts.AccountManager; 25 import android.app.Activity; 26 import android.app.ActivityManagerNative; 27 import android.app.Fragment; 28 import android.app.admin.DevicePolicyManager; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.IPackageManager; 33 import android.content.pm.UserInfo; 34 import android.graphics.Bitmap; 35 import android.graphics.Canvas; 36 import android.graphics.drawable.Drawable; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.os.ServiceManager; 43 import android.os.UserHandle; 44 import android.os.UserManager; 45 import android.preference.PreferenceManager; 46 import android.provider.Settings.Secure; 47 import android.util.Log; 48 import android.view.inputmethod.InputMethodInfo; 49 import android.view.inputmethod.InputMethodManager; 50 51 import com.android.internal.widget.ILockSettings; 52 import com.android.internal.widget.LockPatternUtils; 53 import com.android.internal.widget.LockPatternUtilsCache; 54 import com.android.tv.dialog.PinDialogFragment; 55 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.HashMap; 59 import java.util.HashSet; 60 import java.util.List; 61 import java.util.Set; 62 63 /** 64 * Activity that allows the configuration of a user's restricted profile. 65 */ 66 public class RestrictedProfileActivity extends Activity implements Action.Listener, 67 AppLoadingTask.Listener { 68 69 public static class RestrictedProfilePinDialogFragment extends PinDialogFragment { 70 71 private static final String PREF_DISABLE_PIN_UNTIL = 72 "RestrictedProfileActivity$RestrictedProfilePinDialogFragment.disable_pin_until"; 73 74 /** 75 * Returns the time until we should disable the PIN dialog (because the user input wrong 76 * PINs repeatedly). 77 */ 78 public static final long getDisablePinUntil(Context context) { 79 return PreferenceManager.getDefaultSharedPreferences(context).getLong( 80 PREF_DISABLE_PIN_UNTIL, 0); 81 } 82 83 /** 84 * Saves the time until we should disable the PIN dialog (because the user input wrong PINs 85 * repeatedly). 86 */ 87 public static final void setDisablePinUntil(Context context, long timeMillis) { 88 PreferenceManager.getDefaultSharedPreferences(context).edit().putLong( 89 PREF_DISABLE_PIN_UNTIL, timeMillis).apply(); 90 } 91 92 private final LockPatternUtils mLpu; 93 private final ILockSettings mILockSettings; 94 95 public RestrictedProfilePinDialogFragment(int type, ResultListener listener, 96 LockPatternUtils lpu, ILockSettings iLockSettings) { 97 super(type, listener); 98 mLpu = lpu; 99 mILockSettings = iLockSettings; 100 } 101 102 @Override 103 public long getPinDisabledUntil() { 104 return getDisablePinUntil(getActivity()); 105 } 106 107 @Override 108 public void setPinDisabledUntil(long retryDisableTimeout) { 109 setDisablePinUntil(getActivity(), retryDisableTimeout); 110 } 111 112 @Override 113 public void setPin(String pin) { 114 mLpu.saveLockPassword(pin, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 115 } 116 117 @Override 118 public boolean isPinCorrect(String pin) { 119 try { 120 if (mILockSettings.checkPassword(pin, UserHandle.USER_OWNER)) { 121 return true; 122 } 123 } catch (RemoteException re) { 124 // Do nothing 125 } 126 return false; 127 } 128 129 @Override 130 public boolean isPinSet() { 131 return UserHandle.myUserId() != UserHandle.USER_OWNER || hasLockscreenSecurity(mLpu); 132 } 133 } 134 135 private static final boolean DEBUG = false; 136 private static final String TAG = "RestrictedProfile"; 137 138 private static final String 139 ACTION_RESTRICTED_PROFILE_SETUP_LOCKSCREEN = "restricted_setup_locakscreen"; 140 private static final String ACTION_RESTRICTED_PROFILE_CREATE = "restricted_profile_create"; 141 private static final String 142 ACTION_RESTRICTED_PROFILE_SWITCH_TO = "restricted_profile_switch_to"; 143 private static final String 144 ACTION_RESTRICTED_PROFILE_SWITCH_OUT = "restricted_profile_switch_out"; 145 private static final String ACTION_RESTRICTED_PROFILE_CONFIG = "restricted_profile_config"; 146 private static final String ACTION_RESTRICTED_PROFILE_CONFIG_APPS = "restricted_profile_config_apps"; 147 private static final String ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD = "restricted_profile_change_password"; 148 private static final String ACTION_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete"; 149 private static final String 150 ACTION_RESTRICTED_PROFILE_DELETE_CONFIRM = "restricted_profile_delete_confirm"; 151 private static final String 152 ACTION_RESTRICTED_PROFILE_DELETE_CANCEL = "restricted_profile_delete_cancel"; 153 154 /** 155 * The description string that should be used for an action that launches the restricted profile 156 * activity. 157 * 158 * @param context used to get the appropriate string. 159 * @return the description string that should be used for an action that launches the restricted 160 * profile activity. 161 */ 162 public static String getActionDescription(Context context) { 163 return context.getString(isRestrictedProfileInEffect(context) ? R.string.on : R.string.off); 164 } 165 166 public static boolean isRestrictedProfileInEffect(Context context) { 167 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 168 UserInfo restrictedUserInfo = findRestrictedUser(userManager); 169 boolean isOwner = UserHandle.myUserId() == UserHandle.USER_OWNER; 170 boolean isRestrictedProfileOn = restrictedUserInfo != null && !isOwner; 171 return isRestrictedProfileOn; 172 } 173 174 static void switchUserNow(int userId) { 175 try { 176 ActivityManagerNative.getDefault().switchUser(userId); 177 } catch (RemoteException re) { 178 Log.e(TAG, "Caught exception while switching user! " + re); 179 } 180 } 181 182 static int getIconResource() { 183 return R.drawable.ic_settings_restricted_profile; 184 } 185 186 static UserInfo findRestrictedUser(UserManager userManager) { 187 for (UserInfo userInfo : userManager.getUsers()) { 188 if (userInfo.isRestricted()) { 189 return userInfo; 190 } 191 } 192 return null; 193 } 194 195 private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>(); 196 private final boolean mIsOwner = UserHandle.myUserId() == UserHandle.USER_OWNER; 197 private final AsyncTask<Void, Void, UserInfo> 198 mAddUserAsyncTask = new AsyncTask<Void, Void, UserInfo>() { 199 @Override 200 protected UserInfo doInBackground(Void... params) { 201 UserInfo restrictedUserInfo = mUserManager.createUser( 202 RestrictedProfileActivity.this.getString(R.string.user_new_profile_name), 203 UserInfo.FLAG_RESTRICTED); 204 if (restrictedUserInfo == null) { 205 Log.wtf(TAG, "Got back a null user handle!"); 206 return null; 207 } 208 int userId = restrictedUserInfo.id; 209 UserHandle user = new UserHandle(userId); 210 mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); 211 Secure.putIntForUser(getContentResolver(), Secure.LOCATION_MODE, 212 Secure.LOCATION_MODE_OFF, userId); 213 mUserManager.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user); 214 Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default); 215 mUserManager.setUserIcon(userId, bitmap); 216 // Add shared accounts 217 AccountManager am = AccountManager.get(RestrictedProfileActivity.this); 218 Account[] accounts = am.getAccounts(); 219 if (accounts != null) { 220 for (Account account : accounts) { 221 am.addSharedAccount(account, user); 222 } 223 } 224 return restrictedUserInfo; 225 } 226 227 @Override 228 protected void onPostExecute(UserInfo result) { 229 if (result == null) { 230 return; 231 } 232 mRestrictedUserInfo = result; 233 UserSwitchListenerService.updateLaunchPoint(RestrictedProfileActivity.this, true); 234 int userId = result.id; 235 if (result.isRestricted() && mIsOwner) { 236 DialogFragment dialogFragment = UserAppRestrictionsDialogFragment.newInstance( 237 RestrictedProfileActivity.this, userId, true); 238 DialogFragment.add(getFragmentManager(), dialogFragment); 239 mMainMenuDialogFragment.setActions(getMainMenuActions()); 240 } 241 } 242 }; 243 244 private UserManager mUserManager; 245 private UserInfo mRestrictedUserInfo; 246 private DialogFragment mMainMenuDialogFragment; 247 private ILockSettings mLockSettingsService; 248 private Handler mHandler; 249 private IPackageManager mIPm; 250 private AppLoadingTask mAppLoadingTask; 251 private Action mConfigAppsAction; 252 private DialogFragment mConfigDialogFragment; 253 254 @Override 255 protected void onCreate(Bundle savedInstanceState) { 256 super.onCreate(savedInstanceState); 257 mHandler = new Handler(); 258 mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 259 mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); 260 mRestrictedUserInfo = findRestrictedUser(mUserManager); 261 mConfigAppsAction = createConfigAppsAction(-1); 262 mMainMenuDialogFragment = new DialogFragment.Builder() 263 .title(getString(R.string.launcher_restricted_profile_app_name)) 264 .description(getString(R.string.user_add_profile_item_summary)) 265 .iconResourceId(getIconResource()) 266 .iconBackgroundColor(getResources().getColor(R.color.icon_background)) 267 .actions(getMainMenuActions()).build(); 268 DialogFragment.add(getFragmentManager(), mMainMenuDialogFragment); 269 } 270 271 @Override 272 protected void onResume() { 273 super.onResume(); 274 if (mRestrictedUserInfo != null && (mAppLoadingTask == null 275 || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED)) { 276 mAppLoadingTask = new AppLoadingTask(this, mRestrictedUserInfo.id, false, mIPm, this); 277 mAppLoadingTask.execute((Void[]) null); 278 } 279 } 280 281 @Override 282 public void onPackageEnableChanged(String packageName, boolean enabled) { 283 } 284 285 @Override 286 public void onActionsLoaded(ArrayList<Action> actions) { 287 int allowedApps = 0; 288 for(Action action : actions) { 289 if(action.isChecked()) { 290 allowedApps++; 291 } 292 } 293 mConfigAppsAction = createConfigAppsAction(allowedApps); 294 if (mConfigDialogFragment != null) { 295 mConfigDialogFragment.setActions(getConfigActions()); 296 } 297 } 298 299 @Override 300 public void onActionClicked(Action action) { 301 if (ACTION_RESTRICTED_PROFILE_SWITCH_TO.equals(action.getKey())) { 302 switchUserNow(mRestrictedUserInfo.id); 303 finish(); 304 } else if (ACTION_RESTRICTED_PROFILE_SWITCH_OUT.equals(action.getKey())) { 305 if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) { 306 return; 307 } 308 new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN, 309 new PinDialogFragment.ResultListener() { 310 @Override 311 public void done(boolean success) { 312 if (success) { 313 switchUserNow(UserHandle.USER_OWNER); 314 finish(); 315 } 316 } 317 }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(), 318 PinDialogFragment.DIALOG_TAG); 319 } else if (ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD.equals(action.getKey())) { 320 if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) { 321 return; 322 } 323 new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN, 324 new PinDialogFragment.ResultListener() { 325 @Override 326 public void done(boolean success) { 327 // do nothing 328 } 329 }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(), 330 PinDialogFragment.DIALOG_TAG); 331 } else if (ACTION_RESTRICTED_PROFILE_CONFIG.equals(action.getKey())) { 332 mConfigDialogFragment = new DialogFragment.Builder() 333 .title(getString(R.string.restricted_profile_configure_title)) 334 .iconResourceId(getIconResource()) 335 .iconBackgroundColor(getResources().getColor(R.color.icon_background)) 336 .actions(getConfigActions()).build(); 337 DialogFragment.add(getFragmentManager(), mConfigDialogFragment); 338 } else if (ACTION_RESTRICTED_PROFILE_CONFIG_APPS.equals(action.getKey())) { 339 DialogFragment dialogFragment = UserAppRestrictionsDialogFragment.newInstance( 340 RestrictedProfileActivity.this, mRestrictedUserInfo.id, false); 341 DialogFragment.add(getFragmentManager(), dialogFragment); 342 } else if (ACTION_RESTRICTED_PROFILE_DELETE.equals(action.getKey())) { 343 if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) { 344 return; 345 } 346 new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN, 347 new PinDialogFragment.ResultListener() { 348 @Override 349 public void done(boolean success) { 350 if (success) { 351 removeRestrictedUser(); 352 LockPatternUtils lpu = new LockPatternUtils( 353 RestrictedProfileActivity.this); 354 lpu.clearLock(false); 355 } 356 } 357 }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(), 358 PinDialogFragment.DIALOG_TAG); 359 } else if (ACTION_RESTRICTED_PROFILE_DELETE_CONFIRM.equals(action.getKey())) { 360 // TODO remove once we confirm it's not needed 361 removeRestrictedUser(); 362 LockPatternUtils lpu = new LockPatternUtils(this); 363 lpu.clearLock(false); 364 } else if (ACTION_RESTRICTED_PROFILE_DELETE_CANCEL.equals(action.getKey())) { 365 // TODO remove once we confirm it's not needed 366 onBackPressed(); 367 } else if (ACTION_RESTRICTED_PROFILE_CREATE.equals(action.getKey())) { 368 if (hasLockscreenSecurity(new LockPatternUtils(this))) { 369 addRestrictedUser(); 370 } else { 371 launchChooseLockscreen(); 372 } 373 } 374 } 375 376 private ILockSettings getLockSettings() { 377 if (mLockSettingsService == null) { 378 mLockSettingsService = LockPatternUtilsCache.getInstance( 379 ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"))); 380 } 381 return mLockSettingsService; 382 } 383 384 private ArrayList<Action> getMainMenuActions() { 385 ArrayList<Action> actions = new ArrayList<Action>(); 386 if (mRestrictedUserInfo != null) { 387 if (mIsOwner) { 388 actions.add(new Action.Builder() 389 .key(ACTION_RESTRICTED_PROFILE_SWITCH_TO) 390 .title(getString(R.string.restricted_profile_switch_to)) 391 .build()); 392 actions.add(new Action.Builder() 393 .key(ACTION_RESTRICTED_PROFILE_CONFIG) 394 .title(getString(R.string.restricted_profile_configure_title)) 395 .build()); 396 actions.add(new Action.Builder() 397 .key(ACTION_RESTRICTED_PROFILE_DELETE) 398 .title(getString(R.string.restricted_profile_delete_title)) 399 .build()); 400 } else { 401 actions.add(new Action.Builder() 402 .key(ACTION_RESTRICTED_PROFILE_SWITCH_OUT) 403 .title(getString(R.string.restricted_profile_switch_out)) 404 .build()); 405 } 406 } else { 407 actions.add(new Action.Builder() 408 .key(ACTION_RESTRICTED_PROFILE_CREATE) 409 .title(getString(R.string.restricted_profile_configure_title)) 410 .build()); 411 } 412 return actions; 413 } 414 415 private ArrayList<Action> getConfigActions() { 416 ArrayList<Action> actions = new ArrayList<Action>(); 417 actions.add(new Action.Builder() 418 .key(ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD) 419 .title(getString(R.string.restricted_profile_change_password_title)) 420 .build()); 421 actions.add(mConfigAppsAction); 422 return actions; 423 } 424 425 private Action createConfigAppsAction(int allowedApps) { 426 String description = allowedApps >= 0 ? getResources().getQuantityString( 427 R.plurals.restricted_profile_configure_apps_description, allowedApps, allowedApps) 428 : getString(R.string.restricted_profile_configure_apps_description_loading); 429 return new Action.Builder() 430 .key(ACTION_RESTRICTED_PROFILE_CONFIG_APPS) 431 .title(getString(R.string.restricted_profile_configure_apps_title)) 432 .description(description) 433 .build(); 434 } 435 436 private static boolean hasLockscreenSecurity(LockPatternUtils lpu) { 437 return lpu.isLockPasswordEnabled() || lpu.isLockPatternEnabled(); 438 } 439 440 private void launchChooseLockscreen() { 441 if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) { 442 return; 443 } 444 new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN, 445 new PinDialogFragment.ResultListener() { 446 @Override 447 public void done(boolean success) { 448 if (success) { 449 addRestrictedUser(); 450 } 451 } 452 }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(), 453 PinDialogFragment.DIALOG_TAG); 454 } 455 456 private void removeRestrictedUser() { 457 mHandler.post(new Runnable() { 458 @Override 459 public void run() { 460 mUserManager.removeUser(mRestrictedUserInfo.id); 461 // pop confirm dialog 462 mRestrictedUserInfo = null; 463 UserSwitchListenerService.updateLaunchPoint(RestrictedProfileActivity.this, false); 464 mMainMenuDialogFragment.setActions(getMainMenuActions()); 465 getFragmentManager().popBackStack(); 466 } 467 }); 468 } 469 470 private Bitmap createBitmapFromDrawable(int resId) { 471 Drawable icon = getResources().getDrawable(resId); 472 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 473 Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), 474 Bitmap.Config.ARGB_8888); 475 icon.draw(new Canvas(bitmap)); 476 return bitmap; 477 } 478 479 private void addRestrictedUser() { 480 if (AsyncTask.Status.PENDING == mAddUserAsyncTask.getStatus()) { 481 mAddUserAsyncTask.execute((Void[]) null); 482 } 483 } 484 } 485