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