AppRestrictionsFragment.java revision 80b1dfe46a85e3ecdbeca5575c205086e5ab07e0
1/* 2 * Copyright (C) 2013 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 17package com.android.settings.users; 18 19import android.appwidget.AppWidgetManager; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.RestrictionEntry; 24import android.content.pm.ApplicationInfo; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.content.pm.ResolveInfo; 29import android.content.pm.UserInfo; 30import android.graphics.Bitmap; 31import android.graphics.Color; 32import android.graphics.drawable.BitmapDrawable; 33import android.graphics.drawable.ColorDrawable; 34import android.graphics.drawable.Drawable; 35import android.os.Bundle; 36import android.os.Parcelable; 37import android.os.UserHandle; 38import android.os.UserManager; 39import android.preference.CheckBoxPreference; 40import android.preference.EditTextPreference; 41import android.preference.ListPreference; 42import android.preference.MultiSelectListPreference; 43import android.preference.Preference; 44import android.preference.PreferenceActivity; 45import android.preference.PreferenceCategory; 46import android.preference.Preference.OnPreferenceChangeListener; 47import android.preference.Preference.OnPreferenceClickListener; 48import android.preference.PreferenceGroup; 49import android.preference.SwitchPreference; 50import android.text.InputType; 51import android.text.TextUtils; 52import android.util.Log; 53import android.view.View; 54import android.view.View.OnClickListener; 55import android.view.ViewGroup; 56import android.widget.CompoundButton; 57import android.widget.CompoundButton.OnCheckedChangeListener; 58import android.widget.Switch; 59 60import com.android.settings.R; 61import com.android.settings.SelectableEditTextPreference; 62import com.android.settings.SettingsPreferenceFragment; 63 64import java.util.ArrayList; 65import java.util.Collection; 66import java.util.Collections; 67import java.util.Comparator; 68import java.util.HashMap; 69import java.util.HashSet; 70import java.util.List; 71import java.util.Set; 72import java.util.StringTokenizer; 73 74import libcore.util.CollectionUtils; 75 76public class AppRestrictionsFragment extends SettingsPreferenceFragment implements 77 OnPreferenceChangeListener, OnClickListener, OnPreferenceClickListener { 78 79 private static final String TAG = AppRestrictionsFragment.class.getSimpleName(); 80 81 private static final String PKG_PREFIX = "pkg_"; 82 private static final String KEY_USER_INFO = "user_info"; 83 84 private UserManager mUserManager; 85 private UserHandle mUser; 86 87 private SelectableEditTextPreference mUserPreference; 88 private PreferenceGroup mAppList; 89 90 private static final int MAX_APP_RESTRICTIONS = 100; 91 92 private static final String DELIMITER = ";"; 93 HashMap<String,Boolean> mSelectedPackages = new HashMap<String,Boolean>(); 94 private boolean mFirstTime = true; 95 private boolean mNewUser; 96 97 private int mCustomRequestCode; 98 private HashMap<Integer, AppRestrictionsPreference> mCustomRequestMap = 99 new HashMap<Integer,AppRestrictionsPreference>(); 100 101 public static class Activity extends PreferenceActivity { 102 @Override 103 public Intent getIntent() { 104 Intent modIntent = new Intent(super.getIntent()); 105 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AppRestrictionsFragment.class.getName()); 106 modIntent.putExtra(EXTRA_NO_HEADERS, true); 107 return modIntent; 108 } 109 } 110 111 static class AppRestrictionsPreference extends SwitchPreference { 112 private boolean hasSettings; 113 private OnClickListener listener; 114 private ArrayList<RestrictionEntry> restrictions; 115 boolean panelOpen; 116 private boolean required; 117 List<Preference> childPreferences = new ArrayList<Preference>(); 118 119 AppRestrictionsPreference(Context context, OnClickListener listener) { 120 super(context); 121 setLayoutResource(R.layout.preference_app_restrictions); 122 this.listener = listener; 123 } 124 125 private void setSettingsEnabled(boolean enable) { 126 hasSettings = enable; 127 } 128 129 void setRestrictions(ArrayList<RestrictionEntry> restrictions) { 130 this.restrictions = restrictions; 131 } 132 133 void setRequired(boolean required) { 134 this.required = required; 135 } 136 137 boolean isRequired() { 138 return required; 139 } 140 141 RestrictionEntry getRestriction(String key) { 142 if (restrictions == null) return null; 143 for (RestrictionEntry entry : restrictions) { 144 if (entry.getKey().equals(key)) { 145 return entry; 146 } 147 } 148 return null; 149 } 150 151 ArrayList<RestrictionEntry> getRestrictions() { 152 return restrictions; 153 } 154 155 @Override 156 protected void onBindView(View view) { 157 super.onBindView(view); 158 159 View appRestrictionsSettings = view.findViewById(R.id.app_restrictions_settings); 160 appRestrictionsSettings.setVisibility(hasSettings ? View.VISIBLE : View.GONE); 161 view.findViewById(R.id.settings_divider).setVisibility( 162 hasSettings ? View.VISIBLE : View.GONE); 163 appRestrictionsSettings.setOnClickListener(listener); 164 appRestrictionsSettings.setTag(this); 165 166 View appRestrictionsPref = view.findViewById(R.id.app_restrictions_pref); 167 appRestrictionsPref.setOnClickListener(listener); 168 appRestrictionsPref.setTag(this); 169 170 ViewGroup widget = (ViewGroup) view.findViewById(android.R.id.widget_frame); 171 widget.setEnabled(!isRequired()); 172 if (widget.getChildCount() > 0) { 173 final Switch switchView = (Switch) widget.getChildAt(0); 174 switchView.setEnabled(!isRequired()); 175 switchView.setTag(this); 176 switchView.setOnCheckedChangeListener(new OnCheckedChangeListener() { 177 @Override 178 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 179 listener.onClick(switchView); 180 } 181 }); 182 } 183 } 184 } 185 186 @Override 187 public void onCreate(Bundle icicle) { 188 super.onCreate(icicle); 189 190 mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); 191 addPreferencesFromResource(R.xml.app_restrictions); 192 mAppList = getPreferenceScreen(); 193 mUserPreference = (SelectableEditTextPreference) findPreference(KEY_USER_INFO); 194 mUserPreference.setOnPreferenceChangeListener(this); 195 mUserPreference.getEditText().setInputType( 196 InputType.TYPE_TEXT_VARIATION_NORMAL | InputType.TYPE_TEXT_FLAG_CAP_WORDS); 197 mUserPreference.setInitialSelectionMode( 198 SelectableEditTextPreference.SELECTION_SELECT_ALL); 199 setHasOptionsMenu(true); 200 } 201 202 void setUser(UserHandle user, boolean newUser) { 203 mUser = user; 204 mNewUser = newUser; 205 } 206 207 public void onResume() { 208 super.onResume(); 209 if (mFirstTime) { 210 mFirstTime = false; 211 populateApps(); 212 } 213 UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier()); 214 mUserPreference.setTitle(info.name); 215 Bitmap userIcon = mUserManager.getUserIcon(mUser.getIdentifier()); 216 CircleFramedDrawable circularIcon = 217 CircleFramedDrawable.getInstance(this.getActivity(), userIcon); 218 mUserPreference.setIcon(circularIcon); 219 mUserPreference.setText(info.name); 220 } 221 222 private void addSystemApps(List<ApplicationInfo> visibleApps, Intent intent) { 223 final PackageManager pm = getActivity().getPackageManager(); 224 List<ResolveInfo> launchableApps = pm.queryIntentActivities(intent, 0); 225 for (ResolveInfo app : launchableApps) { 226 if (app.activityInfo != null && app.activityInfo.applicationInfo != null) { 227 int flags = app.activityInfo.applicationInfo.flags; 228 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 229 || (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { 230 // System app 231 visibleApps.add(app.activityInfo.applicationInfo); 232 } 233 } 234 } 235 } 236 237 private void populateApps() { 238 mAppList.setOrderingAsAdded(false); 239 List<ApplicationInfo> visibleApps = new ArrayList<ApplicationInfo>(); 240 // TODO: Do this asynchronously since it can be a long operation 241 final Context context = getActivity(); 242 PackageManager pm = context.getPackageManager(); 243 244 // Add launchers 245 Intent launcherIntent = new Intent(Intent.ACTION_MAIN); 246 launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); 247 addSystemApps(visibleApps, launcherIntent); 248 249 // Add widgets 250 Intent widgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 251 addSystemApps(visibleApps, widgetIntent); 252 253 List<ApplicationInfo> installedApps = pm.getInstalledApplications(0); 254 for (ApplicationInfo app : installedApps) { 255 if ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 256 && (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { 257 // Downloaded app 258 visibleApps.add(app); 259 } 260 } 261 Collections.sort(visibleApps, new AppLabelComparator(pm)); 262 263 for (int i = visibleApps.size() - 1; i > 1; i--) { 264 ApplicationInfo appInfo = visibleApps.get(i); 265 if (appInfo.packageName.equals(visibleApps.get(i-1).packageName)) { 266 visibleApps.remove(i); 267 } 268 } 269 Intent restrictionsIntent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 270 final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(restrictionsIntent, 0); 271 final List<ResolveInfo> existingApps = pm.queryIntentActivitiesAsUser(launcherIntent, 272 0, mUser.getIdentifier()); 273 int i = 0; 274 if (visibleApps.size() > 0) { 275 for (ApplicationInfo app : visibleApps) { 276 if (app.packageName == null) continue; 277 String packageName = app.packageName; 278 Drawable icon = app.loadIcon(pm); 279 CharSequence label = app.loadLabel(pm); 280 AppRestrictionsPreference p = new AppRestrictionsPreference(context, this); 281 p.setIcon(icon); 282 p.setTitle(label); 283 p.setKey(PKG_PREFIX + packageName); 284 p.setSettingsEnabled(hasPackage(receivers, packageName) 285 || packageName.equals(getActivity().getPackageName())); 286 p.setPersistent(false); 287 p.setOnPreferenceChangeListener(this); 288 p.setOnPreferenceClickListener(this); 289 try { 290 PackageInfo pi = pm.getPackageInfo(packageName, 0); 291 if (pi.requiredForAllUsers) { 292 p.setChecked(true); 293 p.setRequired(true); 294 } else if (!mNewUser && hasPackage(existingApps, packageName)) { 295 p.setChecked(true); 296 } 297 } catch (NameNotFoundException re) { 298 // This would be bad 299 } 300 301 mAppList.addPreference(p); 302 if (packageName.equals(getActivity().getPackageName())) { 303 p.setOrder(MAX_APP_RESTRICTIONS * 1); 304 } else { 305 p.setOrder(MAX_APP_RESTRICTIONS * (i + 2)); 306 } 307 mSelectedPackages.put(packageName, p.isChecked()); 308 i++; 309 } 310 } 311 } 312 313 private class AppLabelComparator implements Comparator<ApplicationInfo> { 314 315 PackageManager pm; 316 317 private AppLabelComparator(PackageManager pm) { 318 this.pm = pm; 319 } 320 321 private CharSequence getLabel(ApplicationInfo info) { 322 // TODO: Optimize this with a cache 323 return info.loadLabel(pm); 324 } 325 326 @Override 327 public int compare(ApplicationInfo lhs, ApplicationInfo rhs) { 328 String lhsLabel = getLabel(lhs).toString(); 329 String rhsLabel = getLabel(rhs).toString(); 330 return lhsLabel.compareTo(rhsLabel); 331 } 332 } 333 334 private boolean hasPackage(List<ResolveInfo> receivers, String packageName) { 335 for (ResolveInfo info : receivers) { 336 if (info.activityInfo.packageName.equals(packageName)) { 337 return true; 338 } 339 } 340 return false; 341 } 342 343 @Override 344 public void onClick(View v) { 345 if (v.getTag() instanceof AppRestrictionsPreference) { 346 AppRestrictionsPreference pref = (AppRestrictionsPreference) v.getTag(); 347 if (v.getId() == R.id.app_restrictions_settings) { 348 toggleAppPanel(pref); 349 } else if (!pref.isRequired()) { 350 pref.setChecked(!pref.isChecked()); 351 mSelectedPackages.put(pref.getKey().substring(PKG_PREFIX.length()), 352 pref.isChecked()); 353 } 354 } 355 } 356 357 @Override 358 public boolean onPreferenceChange(Preference preference, Object newValue) { 359 String key = preference.getKey(); 360 if (key != null && key.contains(DELIMITER)) { 361 StringTokenizer st = new StringTokenizer(key, DELIMITER); 362 final String packageName = st.nextToken(); 363 final String restrictionKey = st.nextToken(); 364 AppRestrictionsPreference appPref = (AppRestrictionsPreference) 365 mAppList.findPreference(PKG_PREFIX+packageName); 366 ArrayList<RestrictionEntry> restrictions = appPref.getRestrictions(); 367 if (restrictions != null) { 368 for (RestrictionEntry entry : restrictions) { 369 if (entry.getKey().equals(restrictionKey)) { 370 switch (entry.getType()) { 371 case RestrictionEntry.TYPE_BOOLEAN: 372 entry.setSelectedState((Boolean) newValue); 373 break; 374 case RestrictionEntry.TYPE_CHOICE: 375 case RestrictionEntry.TYPE_CHOICE_LEVEL: 376 ListPreference listPref = (ListPreference) preference; 377 entry.setSelectedString((String) newValue); 378 String readable = findInArray(entry.getChoiceEntries(), 379 entry.getChoiceValues(), (String) newValue); 380 listPref.setSummary(readable); 381 break; 382 case RestrictionEntry.TYPE_MULTI_SELECT: 383 MultiSelectListPreference msListPref = 384 (MultiSelectListPreference) preference; 385 Set<String> set = (Set<String>) newValue; 386 String [] selectedValues = new String[set.size()]; 387 set.toArray(selectedValues); 388 entry.setAllSelectedStrings(selectedValues); 389 break; 390 default: 391 continue; 392 } 393 if (packageName.equals(getActivity().getPackageName())) { 394 RestrictionUtils.setRestrictions(getActivity(), restrictions, mUser); 395 } else { 396 mUserManager.setApplicationRestrictions(packageName, restrictions, 397 mUser); 398 } 399 break; 400 } 401 } 402 } 403 } else if (preference == mUserPreference) { 404 String userName = ((CharSequence) newValue).toString(); 405 if (!TextUtils.isEmpty(userName)) { 406 mUserManager.setUserName(mUser.getIdentifier(), userName); 407 mUserPreference.setTitle(userName); 408 } 409 } 410 return true; 411 } 412 413 private void toggleAppPanel(AppRestrictionsPreference preference) { 414 if (preference.getKey().startsWith(PKG_PREFIX)) { 415 if (preference.panelOpen) { 416 for (Preference p : preference.childPreferences) { 417 mAppList.removePreference(p); 418 } 419 preference.childPreferences.clear(); 420 } else { 421 String packageName = preference.getKey().substring(PKG_PREFIX.length()); 422 if (packageName.equals(getActivity().getPackageName())) { 423 // Settings, fake it by using user restrictions 424 ArrayList<RestrictionEntry> restrictions = RestrictionUtils.getRestrictions( 425 getActivity(), mUser); 426 onRestrictionsReceived(preference, packageName, restrictions); 427 } else { 428 List<RestrictionEntry> oldEntries = 429 mUserManager.getApplicationRestrictions(packageName, mUser); 430 Intent intent = new Intent(Intent.ACTION_GET_RESTRICTION_ENTRIES); 431 intent.setPackage(packageName); 432 intent.putParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS, 433 new ArrayList<RestrictionEntry>(oldEntries)); 434 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 435 getActivity().sendOrderedBroadcast(intent, null, 436 new RestrictionsResultReceiver(packageName, preference), 437 null, Activity.RESULT_OK, null, null); 438 } 439 } 440 preference.panelOpen = !preference.panelOpen; 441 } 442 } 443 444 class RestrictionsResultReceiver extends BroadcastReceiver { 445 446 private static final String CUSTOM_RESTRICTIONS_INTENT = Intent.EXTRA_RESTRICTIONS_INTENT; 447 String packageName; 448 AppRestrictionsPreference preference; 449 450 RestrictionsResultReceiver(String packageName, AppRestrictionsPreference preference) { 451 super(); 452 this.packageName = packageName; 453 this.preference = preference; 454 } 455 456 @Override 457 public void onReceive(Context context, Intent intent) { 458 Bundle results = getResultExtras(true); 459 final ArrayList<RestrictionEntry> restrictions = results.getParcelableArrayList( 460 Intent.EXTRA_RESTRICTIONS); 461 Intent restrictionsIntent = (Intent) results.getParcelable(CUSTOM_RESTRICTIONS_INTENT); 462 if (restrictions != null && restrictionsIntent == null) { 463 onRestrictionsReceived(preference, packageName, restrictions); 464 mUserManager.setApplicationRestrictions(packageName, restrictions, mUser); 465 } else if (restrictionsIntent != null) { 466 final Intent customIntent = restrictionsIntent; 467 customIntent.putParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS, restrictions); 468 Preference p = new Preference(context); 469 p.setTitle(R.string.app_restrictions_custom_label); 470 p.setOnPreferenceClickListener(new OnPreferenceClickListener() { 471 @Override 472 public boolean onPreferenceClick(Preference preference) { 473 int requestCode = generateCustomActivityRequestCode( 474 RestrictionsResultReceiver.this.preference); 475 AppRestrictionsFragment.this.startActivityForResult( 476 customIntent, requestCode); 477 return false; 478 } 479 }); 480 p.setPersistent(false); 481 p.setOrder(preference.getOrder() + 1); 482 preference.childPreferences.add(p); 483 mAppList.addPreference(p); 484 preference.setRestrictions(restrictions); 485 } 486 } 487 } 488 489 private void onRestrictionsReceived(AppRestrictionsPreference preference, String packageName, 490 ArrayList<RestrictionEntry> restrictions) { 491 // Non-custom-activity case - expand the restrictions in-place 492 final Context context = preference.getContext(); 493 int count = 1; 494 for (RestrictionEntry entry : restrictions) { 495 Preference p = null; 496 switch (entry.getType()) { 497 case RestrictionEntry.TYPE_BOOLEAN: 498 p = new CheckBoxPreference(context); 499 p.setTitle(entry.getTitle()); 500 p.setSummary(entry.getDescription()); 501 ((CheckBoxPreference)p).setChecked(entry.getSelectedState()); 502 break; 503 case RestrictionEntry.TYPE_CHOICE: 504 case RestrictionEntry.TYPE_CHOICE_LEVEL: 505 p = new ListPreference(context); 506 p.setTitle(entry.getTitle()); 507 String value = entry.getSelectedString(); 508 if (value == null) { 509 value = entry.getDescription(); 510 } 511 p.setSummary(findInArray(entry.getChoiceEntries(), entry.getChoiceValues(), 512 value)); 513 ((ListPreference)p).setEntryValues(entry.getChoiceValues()); 514 ((ListPreference)p).setEntries(entry.getChoiceEntries()); 515 ((ListPreference)p).setValue(value); 516 break; 517 case RestrictionEntry.TYPE_MULTI_SELECT: 518 p = new MultiSelectListPreference(context); 519 p.setTitle(entry.getTitle()); 520 ((MultiSelectListPreference)p).setEntryValues(entry.getChoiceValues()); 521 ((MultiSelectListPreference)p).setEntries(entry.getChoiceEntries()); 522 HashSet<String> set = new HashSet<String>(); 523 for (String s : entry.getAllSelectedStrings()) { 524 set.add(s); 525 } 526 ((MultiSelectListPreference)p).setValues(set); 527 break; 528 case RestrictionEntry.TYPE_NULL: 529 default: 530 } 531 if (p != null) { 532 p.setPersistent(false); 533 p.setOrder(preference.getOrder() + count); 534 // Store the restrictions key string as a key for the preference 535 p.setKey(preference.getKey().substring(PKG_PREFIX.length()) + DELIMITER 536 + entry.getKey()); 537 mAppList.addPreference(p); 538 p.setOnPreferenceChangeListener(AppRestrictionsFragment.this); 539 preference.childPreferences.add(p); 540 count++; 541 } 542 } 543 preference.setRestrictions(restrictions); 544 } 545 546 /** 547 * Generates a request code that is stored in a map to retrieve the associated 548 * AppRestrictionsPreference. 549 * @param preference 550 * @return 551 */ 552 private int generateCustomActivityRequestCode(AppRestrictionsPreference preference) { 553 mCustomRequestCode++; 554 mCustomRequestMap.put(mCustomRequestCode, preference); 555 return mCustomRequestCode; 556 } 557 558 @Override 559 public void onActivityResult(int requestCode, int resultCode, Intent data) { 560 super.onActivityResult(requestCode, resultCode, data); 561 562 Log.i(TAG, "Got activity resultCode=" + resultCode + ", requestCode=" 563 + requestCode + ", data=" + data); 564 565 AppRestrictionsPreference pref = mCustomRequestMap.get(requestCode); 566 if (pref == null) { 567 Log.w(TAG, "Unknown requestCode " + requestCode); 568 return; 569 } 570 571 if (resultCode == Activity.RESULT_OK) { 572 ArrayList<RestrictionEntry> list = 573 data.getParcelableArrayListExtra(Intent.EXTRA_RESTRICTIONS); 574 if (list != null) { 575 // If there's a valid result, persist it to the user manager. 576 String packageName = pref.getKey().substring(PKG_PREFIX.length()); 577 pref.setRestrictions(list); 578 mUserManager.setApplicationRestrictions(packageName, list, mUser); 579 } 580 toggleAppPanel(pref); 581 } 582 // Remove request from the map 583 mCustomRequestMap.remove(requestCode); 584 } 585 586 private String findInArray(String[] choiceEntries, String[] choiceValues, 587 String selectedString) { 588 for (int i = 0; i < choiceValues.length; i++) { 589 if (choiceValues[i].equals(selectedString)) { 590 return choiceEntries[i]; 591 } 592 } 593 return selectedString; 594 } 595 596 @Override 597 public boolean onPreferenceClick(Preference preference) { 598 if (preference.getKey().startsWith(PKG_PREFIX)) { 599 AppRestrictionsPreference arp = (AppRestrictionsPreference) preference; 600 if (!arp.isRequired()) { 601 arp.setChecked(!arp.isChecked()); 602 mSelectedPackages.put(arp.getKey().substring(PKG_PREFIX.length()), arp.isChecked()); 603 } 604 return true; 605 } 606 return false; 607 } 608} 609