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.app.Activity; 24 import android.app.Fragment; 25 import android.app.FragmentManager; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.RestrictionEntry; 30 import android.content.pm.IPackageManager; 31 import android.os.Bundle; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.text.TextUtils; 36 import android.util.Log; 37 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.HashSet; 41 import java.util.List; 42 43 /** 44 * Handles all aspects of DialogFragment Actions for setting individual app restrictions. 45 */ 46 class AppRestrictionsManager implements Action.Listener { 47 48 interface Listener { 49 void onRestrictionActionsLoaded(String packageName, ArrayList<Action> actions); 50 } 51 52 private static final boolean DEBUG = false; 53 private static final String TAG = "RestrictedProfile"; 54 55 private static final String EXTRA_PACKAGE_NAME = "AppRestrictionsManager.package_name"; 56 private static final String EXTRA_RESTRICTIONS = "AppRestrictionsManager.restrictions"; 57 private static final String EXTRA_USER_HANDLE = "user_handle"; 58 private static final String EXTRA_ENABLED = "enabled"; 59 private static final String EXTRA_RESTRICTION_KEY = "restriction_key"; 60 private static final String EXTRA_CHOICE_VALUE = "choice_value"; 61 private static final String ACTION_CUSTOM_CONFIGURATION = "action_custom_configuration"; 62 private static final String ACTION_TRUE = "action_true"; 63 private static final String ACTION_FALSE = "action_false"; 64 private static final String ACTION_CHANGE_RESTRICTION = "action_change_restriction"; 65 private static final String ACTION_CHOICE = "action_choice"; 66 private static final String ACTION_MULTI_CHOICE = "action_multi_choice"; 67 private static final int MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID = 1; 68 69 private final Fragment mFragment; 70 private final UserManager mUserManager; 71 private final UserHandle mUserHandle; 72 private final IPackageManager mIPm; 73 private final String mPackageName; 74 private final Listener mListener; 75 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 76 private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT; 77 78 @Override 79 public void onReceive(Context context, Intent intent) { 80 Bundle results = getResultExtras(true); 81 mRestrictions = results.getParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST); 82 mRestrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT); 83 84 if (mRestrictions != null && mRestrictionsIntent == null) { 85 saveRestrictions(); 86 } else if (mRestrictionsIntent != null) { 87 getRestrictionActions(); 88 } 89 } 90 }; 91 92 private ArrayList<RestrictionEntry> mRestrictions; 93 private Intent mRestrictionsIntent; 94 private DialogFragment mCurrentDialogFragment; 95 96 /** 97 * @param fragment THe fragment for which DialogFragment actions are being managed. 98 * @param userHandle the user whose app restrictions are being set. 99 * @param userManager the user manager for setting app restrictions. 100 * @param packageName settings' package name (for special case restrictions). 101 * @param listener a listener for when restriciton acitons are ready to be displayed. 102 */ 103 AppRestrictionsManager(Fragment fragment, UserHandle userHandle, UserManager userManager, 104 String packageName, Listener listener) { 105 mFragment = fragment; 106 mUserHandle = userHandle; 107 mUserManager = userManager; 108 mPackageName = packageName; 109 mListener = listener; 110 mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); 111 } 112 113 void loadRestrictionActions() { 114 if (mPackageName.equals(mFragment.getActivity().getPackageName())) { 115 // Settings, fake it by using user restrictions 116 mRestrictions = RestrictionUtils.getRestrictions(mFragment.getActivity(), mUserHandle); 117 mRestrictionsIntent = null; 118 getRestrictionActions(); 119 } else { 120 requestRestrictionsForApp(); 121 } 122 } 123 124 @Override 125 public void onActionClicked(Action action) { 126 String restrictionEntryKey = action.getIntent().getStringExtra(EXTRA_RESTRICTION_KEY); 127 RestrictionEntry entry = restrictionEntryKey != null ? findRestriction(restrictionEntryKey) 128 : null; 129 130 if (ACTION_CUSTOM_CONFIGURATION.equals(action.getKey())) { 131 mFragment.startActivityForResult(action.getIntent(), 1); 132 } else if (ACTION_CHANGE_RESTRICTION.equals(action.getKey())) { 133 ArrayList<Action> actions = new ArrayList<Action>(); 134 switch (entry.getType()) { 135 case RestrictionEntry.TYPE_BOOLEAN: 136 actions.add(new Action.Builder() 137 .key(ACTION_TRUE) 138 .title(Boolean.toString(true)) 139 .checked(entry.getSelectedState()) 140 .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID) 141 .intent(action.getIntent()) 142 .build()); 143 actions.add(new Action.Builder() 144 .key(ACTION_FALSE) 145 .title(Boolean.toString(false)) 146 .checked(!entry.getSelectedState()) 147 .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID) 148 .intent(action.getIntent()) 149 .build()); 150 break; 151 case RestrictionEntry.TYPE_CHOICE: 152 case RestrictionEntry.TYPE_CHOICE_LEVEL: 153 { 154 String value = entry.getSelectedString(); 155 if (value == null) { 156 value = entry.getDescription(); 157 } 158 String[] choiceEntries = entry.getChoiceEntries(); 159 String[] choiceValues = entry.getChoiceValues(); 160 boolean useValue = (choiceEntries == null 161 || choiceEntries.length != choiceValues.length); 162 for (int i = 0; i < choiceValues.length; i++) { 163 String choiceValue = choiceValues[i]; 164 String title = useValue ? choiceValue : choiceEntries[i]; 165 Intent intent = new Intent(action.getIntent()); 166 intent.putExtra(EXTRA_CHOICE_VALUE, choiceValue); 167 actions.add(new Action.Builder() 168 .key(ACTION_CHOICE) 169 .title(title) 170 .checked(choiceValue.equals(value)) 171 .checkSetId(MUTUALLY_EXCLUSIVE_CHOICE_CHECK_SET_ID) 172 .intent(intent) 173 .build()); 174 } 175 } 176 break; 177 case RestrictionEntry.TYPE_MULTI_SELECT: 178 { 179 List<String> selectedChoiceValues = Arrays.asList( 180 entry.getAllSelectedStrings()); 181 String[] choiceEntries = entry.getChoiceEntries(); 182 String[] choiceValues = entry.getChoiceValues(); 183 boolean useValues = (choiceEntries == null 184 || choiceEntries.length != choiceValues.length); 185 for (int i = 0; i < choiceValues.length; i++) { 186 String choiceValue = choiceValues[i]; 187 String title = useValues ? choiceValue : choiceEntries[i]; 188 Intent intent = new Intent(action.getIntent()); 189 intent.putExtra(EXTRA_CHOICE_VALUE, choiceValue); 190 actions.add(new Action.Builder() 191 .key(ACTION_MULTI_CHOICE) 192 .title(title) 193 .checked(selectedChoiceValues.contains(choiceValue)) 194 .intent(intent) 195 .build()); 196 } 197 } 198 break; 199 case RestrictionEntry.TYPE_NULL: 200 default: 201 } 202 203 if (!actions.isEmpty()) { 204 mCurrentDialogFragment = new DialogFragment.Builder() 205 .title(entry.getTitle()) 206 .description(entry.getDescription()) 207 .iconResourceId(RestrictedProfileActivity.getIconResource()) 208 .iconBackgroundColor( 209 mFragment.getActivity().getResources() 210 .getColor(R.color.icon_background)) 211 .actions(actions).build(); 212 mCurrentDialogFragment.setListener(this); 213 DialogFragment.add(mFragment.getFragmentManager(), mCurrentDialogFragment); 214 215 } 216 } else if (ACTION_TRUE.equals(action.getKey())) { 217 entry.setSelectedState(true); 218 saveRestrictions(); 219 } else if (ACTION_FALSE.equals(action.getKey())) { 220 entry.setSelectedState(false); 221 saveRestrictions(); 222 } else if (ACTION_CHOICE.equals(action.getKey())) { 223 entry.setSelectedString(getChoiceValue(action)); 224 saveRestrictions(); 225 } else if (ACTION_MULTI_CHOICE.equals(action.getKey())) { 226 227 action.setChecked(!action.isChecked()); 228 if (mCurrentDialogFragment != null) { 229 mCurrentDialogFragment.setActions(mCurrentDialogFragment.getActions()); 230 } 231 HashSet<String> selectedChoiceValues = new HashSet<String>(); 232 selectedChoiceValues.addAll(Arrays.asList(entry.getAllSelectedStrings())); 233 234 if (action.isChecked()) { 235 selectedChoiceValues.add(getChoiceValue(action)); 236 } else { 237 selectedChoiceValues.remove(getChoiceValue(action)); 238 } 239 entry.setAllSelectedStrings( 240 selectedChoiceValues.toArray(new String[selectedChoiceValues.size()])); 241 saveRestrictions(); 242 } 243 } 244 245 void onActivityResult(int requestCode, int resultCode, Intent data) { 246 if (resultCode == Activity.RESULT_OK) { 247 ArrayList<RestrictionEntry> list = 248 data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS_LIST); 249 Bundle bundle = data.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE); 250 if (list != null) { 251 // If there's a valid result, persist it to the user manager. 252 mUserManager.setApplicationRestrictions(mPackageName, 253 RestrictionUtils.restrictionsToBundle(list), mUserHandle); 254 } else if (bundle != null) { 255 // If there's a valid result, persist it to the user manager. 256 mUserManager.setApplicationRestrictions(mPackageName, bundle, mUserHandle); 257 } 258 } 259 } 260 261 private RestrictionEntry findRestriction(String key) { 262 for (RestrictionEntry entry : mRestrictions) { 263 if (entry.getKey().equals(key)) { 264 return entry; 265 } 266 } 267 Log.wtf(TAG, "Couldn't find the restriction to set!"); 268 return null; 269 } 270 271 private String getChoiceValue(Action action) { 272 return action.getIntent().getStringExtra(EXTRA_CHOICE_VALUE); 273 } 274 275 /** 276 * Send a broadcast to the app to query its restrictions 277 * 278 * @param packageName package name of the app with restrictions 279 */ 280 private void requestRestrictionsForApp() { 281 Bundle oldEntries = mUserManager.getApplicationRestrictions(mPackageName, mUserHandle); 282 Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 283 intent.setPackage(mPackageName); 284 intent.putExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE, oldEntries); 285 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 286 mFragment.getActivity().sendOrderedBroadcast(intent, null, mBroadcastReceiver, null, 287 Activity.RESULT_OK, null, null); 288 } 289 290 private void getRestrictionActions() { 291 ArrayList<Action> actions = new ArrayList<Action>(); 292 293 if (mRestrictionsIntent != null) { 294 actions.add(new Action.Builder() 295 .key(ACTION_CUSTOM_CONFIGURATION) 296 .title(mFragment.getActivity().getString( 297 R.string.restricted_profile_customize_restrictions)) 298 .intent(mRestrictionsIntent) 299 .hasNext(true) 300 .build()); 301 } 302 303 if (mRestrictions != null) { 304 for (RestrictionEntry entry : mRestrictions) { 305 Intent data = new Intent().putExtra(EXTRA_RESTRICTION_KEY, entry.getKey()); 306 switch (entry.getType()) { 307 case RestrictionEntry.TYPE_BOOLEAN: 308 actions.add(new Action.Builder() 309 .key(ACTION_CHANGE_RESTRICTION) 310 .title(entry.getTitle()) 311 .description(mFragment.getActivity().getString( 312 R.string.restriction_description, entry.getDescription(), 313 Boolean.toString(entry.getSelectedState()))) 314 .hasNext(mRestrictionsIntent == null) 315 .infoOnly(mRestrictionsIntent != null) 316 .intent(data) 317 .multilineDescription(true) 318 .build()); 319 break; 320 case RestrictionEntry.TYPE_CHOICE: 321 case RestrictionEntry.TYPE_CHOICE_LEVEL: 322 String value = entry.getSelectedString(); 323 if (value == null) { 324 value = entry.getDescription(); 325 } 326 actions.add(new Action.Builder() 327 .key(ACTION_CHANGE_RESTRICTION) 328 .title(entry.getTitle()) 329 .description(mFragment.getActivity().getString( 330 R.string.restriction_description, 331 entry.getDescription(), findInArray( 332 entry.getChoiceEntries(), entry.getChoiceValues(), 333 value))) 334 .hasNext(mRestrictionsIntent == null) 335 .infoOnly(mRestrictionsIntent != null) 336 .intent(data) 337 .multilineDescription(true) 338 .build()); 339 break; 340 case RestrictionEntry.TYPE_MULTI_SELECT: 341 actions.add(new Action.Builder() 342 .key(ACTION_CHANGE_RESTRICTION) 343 .title(entry.getTitle()) 344 .description(mFragment.getActivity().getString( 345 R.string.restriction_description, 346 entry.getDescription(), TextUtils.join( 347 ", ", findSelectedStrings(entry.getChoiceEntries(), 348 entry.getChoiceValues(), 349 entry.getAllSelectedStrings())))) 350 .hasNext(mRestrictionsIntent == null) 351 .infoOnly(mRestrictionsIntent != null) 352 .intent(data) 353 .multilineDescription(true) 354 .build()); 355 break; 356 case RestrictionEntry.TYPE_NULL: 357 default: 358 } 359 } 360 } 361 362 mListener.onRestrictionActionsLoaded(mPackageName, actions); 363 } 364 365 private String findInArray(String[] choiceEntries, String[] choiceValues, 366 String selectedString) { 367 if (choiceEntries == null || choiceValues.length != choiceEntries.length) { 368 return selectedString; 369 } 370 for (int i = 0; i < choiceValues.length; i++) { 371 if (choiceValues[i].equals(selectedString)) { 372 return choiceEntries[i]; 373 } 374 } 375 return selectedString; 376 } 377 378 private String[] findSelectedStrings(String[] choiceEntries, String[] choiceValues, 379 String[] selectedStrings) { 380 if (choiceEntries == null || choiceValues.length != choiceEntries.length) { 381 return selectedStrings; 382 } 383 String[] selectedStringsMapped = new String[selectedStrings.length]; 384 for (int i = 0; i < selectedStrings.length; i++) { 385 selectedStringsMapped[i] = selectedStrings[i]; 386 for (int j = 0; j < choiceValues.length; j++) { 387 if (choiceValues[j].equals(selectedStrings[i])) { 388 selectedStringsMapped[i] = choiceEntries[j]; 389 } 390 } 391 } 392 return selectedStringsMapped; 393 } 394 395 private void saveRestrictions() { 396 getRestrictionActions(); 397 if (mPackageName.equals(mFragment.getActivity().getPackageName())) { 398 RestrictionUtils.setRestrictions(mFragment.getActivity(), mRestrictions, mUserHandle); 399 } else { 400 Bundle bundle = RestrictionUtils.restrictionsToBundle(mRestrictions); 401 mUserManager.setApplicationRestrictions(mPackageName, bundle, mUserHandle); 402 } 403 } 404 } 405