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 import com.android.tv.settings.users.AppRestrictionsManager.Listener; 23 24 import android.app.Activity; 25 import android.app.Fragment; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.RestrictionEntry; 31 import android.content.pm.ApplicationInfo; 32 import android.content.pm.IPackageManager; 33 import android.content.pm.PackageManager; 34 import android.content.pm.UserInfo; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.net.Uri; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.util.Log; 44 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.Map; 48 49 /** 50 * DialogFragment that configures the app restrictions for a given user. 51 */ 52 public class UserAppRestrictionsDialogFragment extends DialogFragment implements Action.Listener, 53 AppLoadingTask.Listener { 54 55 private static final boolean DEBUG = false; 56 private static final String TAG = "RestrictedProfile"; 57 58 /** Key for extra passed in from calling fragment for the userId of the user being edited */ 59 public static final String EXTRA_USER_ID = "user_id"; 60 61 /** Key for extra passed in from calling fragment to indicate if this is a newly created user */ 62 public static final String EXTRA_NEW_USER = "new_user"; 63 64 private static final String EXTRA_CONTENT_TITLE = "title"; 65 private static final String EXTRA_CONTENT_BREADCRUMB = "breadcrumb"; 66 private static final String EXTRA_CONTENT_DESCRIPTION = "description"; 67 private static final String EXTRA_CONTENT_ICON_RESOURCE_ID = "iconResourceId"; 68 private static final String EXTRA_CONTENT_ICON_URI = "iconUri"; 69 private static final String EXTRA_CONTENT_ICON_BITMAP = "iconBitmap"; 70 private static final String EXTRA_CONTENT_ICON_BACKGROUND = "iconBackground"; 71 72 private static final String EXTRA_PACKAGE_NAME = "packageName"; 73 private static final String EXTRA_CAN_CONFIGURE_RESTRICTIONS = "canConfigureRestrictions"; 74 private static final String EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS = "canSeeRectrictedAccounts"; 75 private static final String EXTRA_IS_ALLOWED = "isAllowed"; 76 private static final String EXTRA_CAN_BE_ENABLED_DISABLED = "canBeEnabledDisabled"; 77 private static final String EXTRA_CONTROLLING_APP = "controllingApp"; 78 79 private static final String ACTION_ALLOW = "allow"; 80 private static final String ACTION_DISALLOW = "disallow"; 81 private static final String ACTION_CONFIGURE = "configure"; 82 private static final String ACTION_CUSTOMIZE_RESTRICTIONS = "customizeRestriction"; 83 84 private static final int CHECK_SET_ID = 1; 85 newInstance(Context context, int userId, boolean newUser)86 public static UserAppRestrictionsDialogFragment newInstance(Context context, int userId, 87 boolean newUser) { 88 UserAppRestrictionsDialogFragment fragment = new UserAppRestrictionsDialogFragment(); 89 Bundle args = new Bundle(); 90 args.putString(EXTRA_CONTENT_TITLE, 91 context.getString(R.string.restricted_profile_configure_apps_title)); 92 args.putInt(EXTRA_CONTENT_ICON_RESOURCE_ID, R.drawable.ic_settings_launcher_icon); 93 args.putInt(EXTRA_CONTENT_ICON_BACKGROUND, 94 context.getResources().getColor(R.color.icon_background)); 95 args.putInt(EXTRA_USER_ID, userId); 96 args.putBoolean(EXTRA_NEW_USER, newUser); 97 fragment.setArguments(args); 98 return fragment; 99 } 100 101 private UserManager mUserManager; 102 private IPackageManager mIPm; 103 private AppLoadingTask mAppLoadingTask; 104 private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>(); 105 private UserHandle mUser; 106 private boolean mNewUser; 107 private boolean mAppListChanged; 108 private AppRestrictionsManager mAppRestrictionsManager; 109 110 private BroadcastReceiver mUserBackgrounding = new BroadcastReceiver() { 111 @Override 112 public void onReceive(Context context, Intent intent) { 113 // Update the user's app selection right away without waiting for a pause 114 // onPause() might come in too late, causing apps to disappear after broadcasts 115 // have been scheduled during user startup. 116 if (mAppListChanged) { 117 if (DEBUG) { 118 Log.d(TAG, "User backgrounding, update app list"); 119 } 120 applyUserAppsStates(mSelectedPackages, getActions(), mIPm, mUser.getIdentifier()); 121 if (DEBUG) { 122 Log.d(TAG, "User backgrounding, done updating app list"); 123 } 124 } 125 } 126 }; 127 private BroadcastReceiver mPackageObserver = new BroadcastReceiver() { 128 @Override 129 public void onReceive(Context context, Intent intent) { 130 onPackageChanged(intent); 131 } 132 133 private void onPackageChanged(Intent intent) { 134 String action = intent.getAction(); 135 String packageName = intent.getData().getSchemeSpecificPart(); 136 // Package added, check if the preference needs to be enabled 137 ArrayList<Action> matchingActions = findActionsWithPackageName(getActions(), 138 packageName); 139 for (Action matchingAction : matchingActions) { 140 if ((Intent.ACTION_PACKAGE_ADDED.equals(action) && matchingAction.isChecked()) || ( 141 Intent.ACTION_PACKAGE_REMOVED.equals(action) 142 && !matchingAction.isChecked())) { 143 matchingAction.setEnabled(true); 144 } 145 } 146 } 147 }; 148 149 @Override onCreate(Bundle icicle)150 public void onCreate(Bundle icicle) { 151 super.onCreate(icicle); 152 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 153 setActions(new ArrayList<Action>()); 154 setListener(this); 155 if (icicle != null) { 156 mUser = new UserHandle(icicle.getInt(EXTRA_USER_ID)); 157 } else { 158 Bundle args = getArguments(); 159 if (args != null) { 160 if (args.containsKey(EXTRA_USER_ID)) { 161 mUser = new UserHandle(args.getInt(EXTRA_USER_ID)); 162 } 163 mNewUser = args.getBoolean(EXTRA_NEW_USER, false); 164 } 165 } 166 167 if (mUser == null) { 168 mUser = android.os.Process.myUserHandle(); 169 } 170 171 mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 172 } 173 174 @Override onResume()175 public void onResume() { 176 super.onResume(); 177 178 getActivity().registerReceiver(mUserBackgrounding, 179 new IntentFilter(Intent.ACTION_USER_BACKGROUND)); 180 IntentFilter packageFilter = new IntentFilter(); 181 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 182 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 183 packageFilter.addDataScheme("package"); 184 getActivity().registerReceiver(mPackageObserver, packageFilter); 185 186 if (mAppLoadingTask == null) { 187 mAppListChanged = false; 188 mAppLoadingTask = new AppLoadingTask(getActivity(), mUser.getIdentifier(), mNewUser, 189 mIPm, this); 190 mAppLoadingTask.execute((Void[]) null); 191 } 192 } 193 194 @Override onPause()195 public void onPause() { 196 super.onPause(); 197 mNewUser = false; 198 getActivity().unregisterReceiver(mUserBackgrounding); 199 getActivity().unregisterReceiver(mPackageObserver); 200 if (mAppListChanged) { 201 new Thread() { 202 public void run() { 203 applyUserAppsStates(mSelectedPackages, getActions(), mIPm, 204 mUser.getIdentifier()); 205 } 206 }.start(); 207 } 208 } 209 210 @Override onActionClicked(Action action)211 public void onActionClicked(Action action) { 212 String packageName = action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME); 213 if (ACTION_CONFIGURE.equals(action.getKey())) { 214 boolean isAllowed = action.getIntent().getBooleanExtra(EXTRA_IS_ALLOWED, false); 215 boolean canBeEnabledDisabled = action.getIntent().getBooleanExtra( 216 EXTRA_CAN_BE_ENABLED_DISABLED, false); 217 String controllingActivity = action.getIntent().getStringExtra(EXTRA_CONTROLLING_APP); 218 final ArrayList<Action> initialAllowDisallowActions = new ArrayList<Action>(); 219 if (controllingActivity != null) { 220 initialAllowDisallowActions.add(new Action.Builder() 221 .title(getString(isAllowed ? R.string.restricted_profile_allowed 222 : R.string.restricted_profile_not_allowed)) 223 .description(getString(R.string.user_restrictions_controlled_by, 224 controllingActivity)) 225 .checked(isAllowed) 226 .infoOnly(true) 227 .build()); 228 } else if (!canBeEnabledDisabled) { 229 initialAllowDisallowActions.add(new Action.Builder() 230 .title(getString(isAllowed ? R.string.restricted_profile_allowed 231 : R.string.restricted_profile_not_allowed)) 232 .checked(isAllowed) 233 .infoOnly(true) 234 .build()); 235 } else { 236 boolean canSeeRestrictedAccounts = action.getIntent().getBooleanExtra( 237 EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS, true); 238 initialAllowDisallowActions.add(new Action.Builder().key(ACTION_DISALLOW) 239 .title(getString(R.string.restricted_profile_not_allowed)) 240 .intent(action.getIntent()) 241 .checked(!isAllowed) 242 .checkSetId(CHECK_SET_ID).build()); 243 initialAllowDisallowActions.add(new Action.Builder().key(ACTION_ALLOW) 244 .title(getString(R.string.restricted_profile_allowed)) 245 .description(canSeeRestrictedAccounts ? getString( 246 R.string.app_sees_restricted_accounts) 247 : null) 248 .intent(action.getIntent()) 249 .checkSetId(CHECK_SET_ID) 250 .checked(isAllowed) 251 .build()); 252 } 253 254 final DialogFragment dialogFragment = new DialogFragment.Builder() 255 .title(action.getTitle()).iconUri(action.getIconUri()) 256 .actions(initialAllowDisallowActions).build(); 257 258 boolean canConfigureRestrictions = action.getIntent().getBooleanExtra( 259 EXTRA_CAN_CONFIGURE_RESTRICTIONS, false); 260 if (canConfigureRestrictions) { 261 mAppRestrictionsManager = new AppRestrictionsManager(this, mUser, 262 mUserManager, packageName, new AppRestrictionsManager.Listener() { 263 @Override 264 public void onRestrictionActionsLoaded(String packageName, 265 ArrayList<Action> restrictionActions) { 266 ArrayList<Action> oldActions = dialogFragment.getActions(); 267 ArrayList<Action> newActions = new ArrayList<Action>(); 268 if (oldActions != null && oldActions.size() 269 >= initialAllowDisallowActions.size()) { 270 for (int i = 0, size = initialAllowDisallowActions.size(); 271 i < size; i++) { 272 newActions.add(oldActions.get(i)); 273 } 274 } else { 275 newActions.addAll(initialAllowDisallowActions); 276 } 277 newActions.addAll(restrictionActions); 278 dialogFragment.setActions(newActions); 279 } 280 }); 281 mAppRestrictionsManager.loadRestrictionActions(); 282 } 283 284 dialogFragment.setListener(this); 285 DialogFragment.add(getFragmentManager(), dialogFragment); 286 } else if (ACTION_ALLOW.equals(action.getKey())) { 287 setEnabled(packageName, true); 288 } else if (ACTION_DISALLOW.equals(action.getKey())) { 289 setEnabled(packageName, false); 290 } else if (mAppRestrictionsManager != null) { 291 mAppRestrictionsManager.onActionClicked(action); 292 } 293 } 294 295 @Override onActivityResult(int requestCode, int resultCode, Intent data)296 public void onActivityResult(int requestCode, int resultCode, Intent data) { 297 if (mAppRestrictionsManager != null) { 298 mAppRestrictionsManager.onActivityResult(requestCode, resultCode, data); 299 } 300 } 301 setEnabled(String packageName, boolean enabled)302 private void setEnabled(String packageName, boolean enabled) { 303 onPackageEnableChanged(packageName, enabled); 304 for (Action action : getActions()) { 305 String actionPackageName = action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME); 306 if (actionPackageName.equals(packageName)) { 307 action.setChecked(enabled); 308 action.setDescription(getString(enabled ? R.string.restricted_profile_allowed 309 : R.string.restricted_profile_not_allowed)); 310 action.getIntent().putExtra(EXTRA_IS_ALLOWED, enabled); 311 } 312 } 313 onActionsLoaded(getActions()); 314 } 315 316 @Override onPackageEnableChanged(String packageName, boolean enabled)317 public void onPackageEnableChanged(String packageName, boolean enabled) { 318 mSelectedPackages.put(packageName, enabled); 319 mAppListChanged = true; 320 if (getActivity() instanceof AppLoadingTask.Listener) { 321 ((AppLoadingTask.Listener) getActivity()).onPackageEnableChanged(packageName, enabled); 322 } 323 } 324 325 @Override onActionsLoaded(ArrayList<Action> actions)326 public void onActionsLoaded(ArrayList<Action> actions) { 327 setActions(actions); 328 if (getActivity() instanceof AppLoadingTask.Listener) { 329 ((AppLoadingTask.Listener) getActivity()).onActionsLoaded(actions); 330 } 331 } 332 createAction(Context context, String packageName, String title, Uri iconUri, boolean canBeEnabledDisabled, boolean isAllowed, boolean hasCustomizableRestrictions, boolean canSeeRestrictedAccounts, boolean availableForRestrictedProfile, String controllingActivity)333 static Action createAction(Context context, String packageName, String title, Uri iconUri, 334 boolean canBeEnabledDisabled, boolean isAllowed, boolean hasCustomizableRestrictions, 335 boolean canSeeRestrictedAccounts, boolean availableForRestrictedProfile, 336 String controllingActivity) { 337 String description = context.getString( 338 availableForRestrictedProfile ? isAllowed ? R.string.restricted_profile_allowed 339 : R.string.restricted_profile_not_allowed 340 : R.string.app_not_supported_in_limited); 341 342 Intent intent = new Intent().putExtra(EXTRA_IS_ALLOWED, isAllowed) 343 .putExtra(EXTRA_CAN_CONFIGURE_RESTRICTIONS, 344 (hasCustomizableRestrictions && (controllingActivity == null))) 345 .putExtra(EXTRA_CAN_BE_ENABLED_DISABLED, canBeEnabledDisabled) 346 .putExtra(EXTRA_PACKAGE_NAME, packageName) 347 .putExtra(EXTRA_CONTROLLING_APP, controllingActivity) 348 .putExtra(EXTRA_CAN_SEE_RESTRICTED_ACCOUNTS, canSeeRestrictedAccounts); 349 350 if (DEBUG) { 351 Log.d(TAG, "Icon uri: " + (iconUri != null ? iconUri.toString() : "null")); 352 } 353 return new Action.Builder() 354 .key(ACTION_CONFIGURE) 355 .title(title) 356 .description(description) 357 .enabled(availableForRestrictedProfile) 358 .iconUri(iconUri) 359 .checked(isAllowed) 360 .intent(intent) 361 .build(); 362 } 363 applyUserAppsStates(HashMap<String, Boolean> selectedPackages, ArrayList<Action> actions, IPackageManager ipm, int userId)364 static void applyUserAppsStates(HashMap<String, Boolean> selectedPackages, 365 ArrayList<Action> actions, IPackageManager ipm, int userId) { 366 for (Map.Entry<String, Boolean> entry : selectedPackages.entrySet()) { 367 String packageName = entry.getKey(); 368 boolean enabled = entry.getValue(); 369 if (applyUserAppState(ipm, userId, packageName, enabled)) { 370 disableActionForPackage(actions, packageName); 371 } 372 } 373 } 374 applyUserAppState(IPackageManager ipm, int userId, String packageName, boolean enabled)375 static boolean applyUserAppState(IPackageManager ipm, int userId, String packageName, 376 boolean enabled) { 377 boolean disableActionForPackage = false; 378 if (enabled) { 379 // Enable selected apps 380 try { 381 ApplicationInfo info = ipm.getApplicationInfo(packageName, 382 PackageManager.GET_UNINSTALLED_PACKAGES, userId); 383 if (info == null || info.enabled == false 384 || (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 385 ipm.installExistingPackageAsUser(packageName, userId); 386 if (DEBUG) { 387 Log.d(TAG, "Installing " + packageName); 388 } 389 } 390 if (info != null && (info.flags & ApplicationInfo.FLAG_HIDDEN) != 0 391 && (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { 392 disableActionForPackage = true; 393 ipm.setApplicationHiddenSettingAsUser(packageName, false, userId); 394 if (DEBUG) { 395 Log.d(TAG, "Unhiding " + packageName); 396 } 397 } 398 } catch (RemoteException re) { 399 Log.e(TAG, "Caught exception while installing " + packageName + "!", re); 400 } 401 } else { 402 // Blacklist all other apps, system or downloaded 403 try { 404 ApplicationInfo info = ipm.getApplicationInfo(packageName, 0, userId); 405 if (info != null) { 406 ipm.deletePackageAsUser(packageName, null, userId, 407 PackageManager.DELETE_SYSTEM_APP); 408 if (DEBUG) { 409 Log.d(TAG, "Uninstalling " + packageName); 410 } 411 } 412 } catch (RemoteException re) { 413 Log.e(TAG, "Caught exception while uninstalling " + packageName + "!", re); 414 } 415 } 416 return disableActionForPackage; 417 } 418 disableActionForPackage(ArrayList<Action> actions, String packageName)419 private static void disableActionForPackage(ArrayList<Action> actions, String packageName) { 420 ArrayList<Action> matchingActions = findActionsWithPackageName(actions, packageName); 421 for(Action matchingAction : matchingActions) { 422 matchingAction.setEnabled(false); 423 } 424 } 425 findActionsWithPackageName(ArrayList<Action> actions, String packageName)426 private static ArrayList<Action> findActionsWithPackageName(ArrayList<Action> actions, 427 String packageName) { 428 ArrayList<Action> matchingActions = new ArrayList<Action>(); 429 if (packageName != null) { 430 for (Action action : actions) { 431 if (packageName.equals(action.getIntent().getStringExtra(EXTRA_PACKAGE_NAME))) { 432 matchingActions.add(action); 433 } 434 } 435 } 436 return matchingActions; 437 } 438 } 439