ManageApplications.java revision 069732c5f914d75231d0c562ac07cbb23860fc6e
1/* 2 * Copyright (C) 2006 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.applications; 18 19import android.app.Activity; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.ApplicationInfo; 23import android.content.pm.IntentFilterVerificationInfo; 24import android.content.pm.PackageManager; 25import android.os.Bundle; 26import android.os.Environment; 27import android.os.UserHandle; 28import android.os.UserManager; 29import android.preference.PreferenceFrameLayout; 30import android.provider.Settings; 31import android.util.ArraySet; 32import android.util.Log; 33import android.view.LayoutInflater; 34import android.view.Menu; 35import android.view.MenuInflater; 36import android.view.MenuItem; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.animation.AnimationUtils; 40import android.widget.AbsListView; 41import android.widget.AdapterView; 42import android.widget.AdapterView.OnItemClickListener; 43import android.widget.AdapterView.OnItemSelectedListener; 44import android.widget.ArrayAdapter; 45import android.widget.BaseAdapter; 46import android.widget.Filter; 47import android.widget.Filterable; 48import android.widget.ListView; 49import android.widget.Spinner; 50 51import com.android.internal.logging.MetricsLogger; 52import com.android.settings.AppHeader; 53import com.android.settings.HelpUtils; 54import com.android.settings.InstrumentedFragment; 55import com.android.settings.R; 56import com.android.settings.Settings.AllApplicationsActivity; 57import com.android.settings.Settings.DomainsURLsAppListActivity; 58import com.android.settings.Settings.HighPowerApplicationsActivity; 59import com.android.settings.Settings.NotificationAppListActivity; 60import com.android.settings.Settings.StorageUseActivity; 61import com.android.settings.Settings.UsageAccessSettingsActivity; 62import com.android.settings.SettingsActivity; 63import com.android.settings.Utils; 64import com.android.settings.applications.AppStateUsageBridge.UsageState; 65import com.android.settings.applications.ApplicationsState.AppEntry; 66import com.android.settings.applications.ApplicationsState.AppFilter; 67import com.android.settings.applications.ApplicationsState.CompoundFilter; 68import com.android.settings.applications.ApplicationsState.VolumeFilter; 69import com.android.settings.fuelgauge.HighPowerDetail; 70import com.android.settings.notification.NotificationBackend; 71import com.android.settings.notification.NotificationBackend.AppRow; 72 73import java.util.ArrayList; 74import java.util.Collections; 75import java.util.Comparator; 76import java.util.List; 77 78/** 79 * Activity to pick an application that will be used to display installation information and 80 * options to uninstall/delete user data for system applications. This activity 81 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE 82 * intent. 83 */ 84public class ManageApplications extends InstrumentedFragment 85 implements OnItemClickListener, OnItemSelectedListener { 86 87 static final String TAG = "ManageApplications"; 88 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 89 90 // Intent extras. 91 public static final String EXTRA_CLASSNAME = "classname"; 92 // Used for storage only. 93 public static final String EXTRA_VOLUME_UUID = "volumeUuid"; 94 public static final String EXTRA_VOLUME_NAME = "volumeName"; 95 96 private static final String EXTRA_SORT_ORDER = "sortOrder"; 97 98 // attributes used as keys when passing values to InstalledAppDetails activity 99 public static final String APP_CHG = "chg"; 100 101 // constant value that can be used to check return code from sub activity. 102 private static final int INSTALLED_APP_DETAILS = 1; 103 private static final int ADVANCED_SETTINGS = 2; 104 105 public static final int SIZE_TOTAL = 0; 106 public static final int SIZE_INTERNAL = 1; 107 public static final int SIZE_EXTERNAL = 2; 108 109 // Filter options used for displayed list of applications 110 // The order which they appear is the order they will show when spinner is present. 111 public static final int FILTER_APPS_ALL = 0; 112 public static final int FILTER_APPS_ENABLED = 1; 113 public static final int FILTER_APPS_DISABLED = 2; 114 public static final int FILTER_APPS_BLOCKED = 3; 115 public static final int FILTER_APPS_PRIORITY = 4; 116 public static final int FILTER_APPS_NO_PEEKING = 5; 117 public static final int FILTER_APPS_SENSITIVE = 6; 118 public static final int FILTER_APPS_PERSONAL = 7; 119 public static final int FILTER_APPS_WORK = 8; 120 public static final int FILTER_APPS_WITH_DOMAIN_URLS = 9; 121 public static final int FILTER_APPS_USAGE_ACCESS = 10; 122 public static final int FILTER_APPS_POWER_WHITELIST = 11; 123 public static final int FILTER_APPS_POWER_NO_WHITELIST = 12; 124 125 // This is the string labels for the filter modes above, the order must be kept in sync. 126 public static final int[] FILTER_LABELS = new int[] { 127 R.string.filter_all_apps, // All apps 128 R.string.filter_enabled_apps, // Enabled 129 R.string.filter_apps_disabled, // Disabled 130 R.string.filter_notif_blocked_apps, // Blocked Notifications 131 R.string.filter_notif_priority_apps, // Priority Notifications 132 R.string.filter_notif_no_peeking, // No peeking Notifications 133 R.string.filter_notif_sensitive_apps, // Sensitive Notifications 134 R.string.filter_personal_apps, // Personal 135 R.string.filter_work_apps, // Work 136 R.string.filter_with_domain_urls_apps, // Domain URLs 137 R.string.filter_all_apps, // Usage access screen, never displayed 138 R.string.high_power_on, // High power whitelist, on 139 R.string.high_power_off, // High power whitelist, off 140 }; 141 // This is the actual mapping to filters from FILTER_ constants above, the order must 142 // be kept in sync. 143 public static final AppFilter[] FILTERS = new AppFilter[] { 144 ApplicationsState.FILTER_EVERYTHING, // All apps 145 ApplicationsState.FILTER_ALL_ENABLED, // Enabled 146 ApplicationsState.FILTER_DISABLED, // Disabled 147 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED, // Blocked Notifications 148 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_PRIORITY, // Priority Notifications 149 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_NO_PEEK, // No peeking Notifications 150 AppStateNotificationBridge.FILTER_APP_NOTIFICATION_SENSITIVE, // Sensitive Notifications 151 ApplicationsState.FILTER_PERSONAL, // Personal 152 ApplicationsState.FILTER_WORK, // Work 153 ApplicationsState.FILTER_WITH_DOMAIN_URLS, // Apps with Domain URLs 154 AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs 155 AppStatePowerBridge.FILTER_POWER_WHITELISTED, // High power whitelist, on 156 AppStatePowerBridge.FILTER_POWER_NOT_WHITELISTED, // High power whitelist, off 157 }; 158 159 // sort order 160 private int mSortOrder = R.id.sort_order_alpha; 161 162 // whether showing system apps. 163 private boolean mShowSystem; 164 private boolean mHasDisabledApps; 165 166 private ApplicationsState mApplicationsState; 167 168 public int mListType; 169 public int mFilter; 170 171 public ApplicationsAdapter mApplications; 172 173 private View mLoadingContainer; 174 175 private View mListContainer; 176 177 // ListView used to display list 178 private ListView mListView; 179 180 // Size resource used for packages whose size computation failed for some reason 181 CharSequence mInvalidSizeStr; 182 183 // layout inflater object used to inflate views 184 private LayoutInflater mInflater; 185 186 private String mCurrentPkgName; 187 private int mCurrentUid; 188 189 private Menu mOptionsMenu; 190 191 public static final int LIST_TYPE_MAIN = 0; 192 public static final int LIST_TYPE_NOTIFICATION = 1; 193 public static final int LIST_TYPE_DOMAINS_URLS = 2; 194 public static final int LIST_TYPE_STORAGE = 3; 195 public static final int LIST_TYPE_USAGE_ACCESS = 4; 196 public static final int LIST_TYPE_HIGH_POWER = 5; 197 198 private View mRootView; 199 200 private View mSpinnerHeader; 201 private Spinner mFilterSpinner; 202 private FilterSpinnerAdapter mFilterAdapter; 203 private NotificationBackend mNotifBackend; 204 private ResetAppsHelper mResetAppsHelper; 205 private String mVolumeUuid; 206 private String mVolumeName; 207 208 @Override 209 public void onCreate(Bundle savedInstanceState) { 210 super.onCreate(savedInstanceState); 211 setHasOptionsMenu(true); 212 mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); 213 214 Intent intent = getActivity().getIntent(); 215 Bundle args = getArguments(); 216 String className = args != null ? args.getString(EXTRA_CLASSNAME) : null; 217 if (className == null) { 218 className = intent.getComponent().getClassName(); 219 } 220 if (className.equals(AllApplicationsActivity.class.getName())) { 221 mShowSystem = true; 222 } else if (className.equals(NotificationAppListActivity.class.getName())) { 223 mListType = LIST_TYPE_NOTIFICATION; 224 mNotifBackend = new NotificationBackend(); 225 } else if (className.equals(DomainsURLsAppListActivity.class.getName())) { 226 mListType = LIST_TYPE_DOMAINS_URLS; 227 } else if (className.equals(StorageUseActivity.class.getName())) { 228 if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) { 229 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID); 230 mVolumeName = args.getString(EXTRA_VOLUME_NAME); 231 mListType = LIST_TYPE_STORAGE; 232 } else { 233 // No volume selected, display a normal list, sorted by size. 234 mListType = LIST_TYPE_MAIN; 235 } 236 mSortOrder = R.id.sort_order_size; 237 } else if (className.equals(UsageAccessSettingsActivity.class.getName())) { 238 mListType = LIST_TYPE_USAGE_ACCESS; 239 getActivity().getActionBar().setTitle(R.string.usage_access_title); 240 } else if (className.equals(HighPowerApplicationsActivity.class.getName())) { 241 mListType = LIST_TYPE_HIGH_POWER; 242 } else { 243 mListType = LIST_TYPE_MAIN; 244 } 245 mFilter = getDefaultFilter(); 246 247 if (savedInstanceState != null) { 248 mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder); 249 } 250 251 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 252 253 mResetAppsHelper = new ResetAppsHelper(getActivity()); 254 } 255 256 257 @Override 258 public View onCreateView(LayoutInflater inflater, ViewGroup container, 259 Bundle savedInstanceState) { 260 // initialize the inflater 261 mInflater = inflater; 262 263 mRootView = inflater.inflate(R.layout.manage_applications_apps, null); 264 mLoadingContainer = mRootView.findViewById(R.id.loading_container); 265 mLoadingContainer.setVisibility(View.VISIBLE); 266 mListContainer = mRootView.findViewById(R.id.list_container); 267 if (mListContainer != null) { 268 // Create adapter and list view here 269 View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); 270 ListView lv = (ListView) mListContainer.findViewById(android.R.id.list); 271 if (emptyView != null) { 272 lv.setEmptyView(emptyView); 273 } 274 lv.setOnItemClickListener(this); 275 lv.setSaveEnabled(true); 276 lv.setItemsCanFocus(true); 277 lv.setTextFilterEnabled(true); 278 mListView = lv; 279 mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter); 280 mListView.setAdapter(mApplications); 281 mListView.setRecyclerListener(mApplications); 282 283 Utils.prepareCustomPreferencesList(container, mRootView, mListView, false); 284 } 285 286 // We have to do this now because PreferenceFrameLayout looks at it 287 // only when the view is added. 288 if (container instanceof PreferenceFrameLayout) { 289 ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true; 290 } 291 292 createHeader(); 293 294 mResetAppsHelper.onRestoreInstanceState(savedInstanceState); 295 296 return mRootView; 297 } 298 299 private void createHeader() { 300 Activity activity = getActivity(); 301 View content = activity.findViewById(R.id.main_content); 302 ViewGroup contentParent = (ViewGroup) content.getParent(); 303 mSpinnerHeader = (ViewGroup) activity.getLayoutInflater() 304 .inflate(R.layout.apps_filter_spinner, contentParent, false); 305 mFilterSpinner = (Spinner) mSpinnerHeader.findViewById(R.id.filter_spinner); 306 mFilterAdapter = new FilterSpinnerAdapter(this); 307 mFilterSpinner.setAdapter(mFilterAdapter); 308 mFilterSpinner.setOnItemSelectedListener(this); 309 contentParent.addView(mSpinnerHeader, 0); 310 311 mFilterAdapter.enableFilter(getDefaultFilter()); 312 if (mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION) { 313 if (UserManager.get(getActivity()).getUserProfiles().size() > 1) { 314 mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL); 315 mFilterAdapter.enableFilter(FILTER_APPS_WORK); 316 } 317 } 318 if (mListType == LIST_TYPE_NOTIFICATION) { 319 mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED); 320 mFilterAdapter.enableFilter(FILTER_APPS_PRIORITY); 321 mFilterAdapter.enableFilter(FILTER_APPS_SENSITIVE); 322 mFilterAdapter.enableFilter(FILTER_APPS_NO_PEEKING); 323 } 324 if (mListType == LIST_TYPE_HIGH_POWER) { 325 mFilterAdapter.enableFilter(FILTER_APPS_POWER_NO_WHITELIST); 326 } 327 if (mListType == LIST_TYPE_STORAGE) { 328 mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid)); 329 } 330 } 331 332 @Override 333 public void onViewCreated(View view, Bundle savedInstanceState) { 334 super.onViewCreated(view, savedInstanceState); 335 if (mListType == LIST_TYPE_STORAGE) { 336 AppHeader.createAppHeader(getActivity(), null, mVolumeName, null); 337 } 338 } 339 340 private int getDefaultFilter() { 341 switch (mListType) { 342 case LIST_TYPE_DOMAINS_URLS: 343 return FILTER_APPS_WITH_DOMAIN_URLS; 344 case LIST_TYPE_USAGE_ACCESS: 345 return FILTER_APPS_USAGE_ACCESS; 346 case LIST_TYPE_HIGH_POWER: 347 return FILTER_APPS_POWER_WHITELIST; 348 default: 349 return FILTER_APPS_ALL; 350 } 351 } 352 353 @Override 354 protected int getMetricsCategory() { 355 switch (mListType) { 356 case LIST_TYPE_MAIN: 357 return MetricsLogger.MANAGE_APPLICATIONS; 358 case LIST_TYPE_NOTIFICATION: 359 return MetricsLogger.MANAGE_APPLICATIONS_NOTIFICATIONS; 360 case LIST_TYPE_DOMAINS_URLS: 361 return MetricsLogger.MANAGE_DOMAIN_URLS; 362 case LIST_TYPE_STORAGE: 363 return InstrumentedFragment.VIEW_CATEGORY_STORAGE_APPS; 364 case LIST_TYPE_USAGE_ACCESS: 365 return MetricsLogger.USAGE_ACCESS; 366 case LIST_TYPE_HIGH_POWER: 367 return InstrumentedFragment.VIEW_CATEGORY_HIGH_POWER_APPS; 368 default: 369 return MetricsLogger.VIEW_UNKNOWN; 370 } 371 } 372 373 @Override 374 public void onResume() { 375 super.onResume(); 376 updateView(); 377 updateOptionsMenu(); 378 if (mApplications != null) { 379 mApplications.resume(mSortOrder); 380 mApplications.updateLoading(); 381 } 382 } 383 384 @Override 385 public void onSaveInstanceState(Bundle outState) { 386 super.onSaveInstanceState(outState); 387 mResetAppsHelper.onSaveInstanceState(outState); 388 outState.putInt(EXTRA_SORT_ORDER, mSortOrder); 389 } 390 391 @Override 392 public void onPause() { 393 super.onPause(); 394 if (mApplications != null) { 395 mApplications.pause(); 396 } 397 } 398 399 @Override 400 public void onStop() { 401 super.onStop(); 402 mResetAppsHelper.stop(); 403 } 404 405 @Override 406 public void onDestroyView() { 407 super.onDestroyView(); 408 409 if (mApplications != null) { 410 mApplications.release(); 411 } 412 mRootView = null; 413 } 414 415 @Override 416 public void onActivityResult(int requestCode, int resultCode, Intent data) { 417 if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { 418 if (mListType == LIST_TYPE_NOTIFICATION) { 419 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid); 420 } else { 421 mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid)); 422 } 423 } 424 } 425 426 // utility method used to start sub activity 427 private void startApplicationDetailsActivity() { 428 Activity activity = getActivity(); 429 switch (mListType) { 430 case LIST_TYPE_NOTIFICATION: 431 activity.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) 432 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 433 .putExtra(Settings.EXTRA_APP_PACKAGE, mCurrentPkgName) 434 .putExtra(Settings.EXTRA_APP_UID, mCurrentUid)); 435 break; 436 case LIST_TYPE_DOMAINS_URLS: 437 startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label); 438 break; 439 case LIST_TYPE_USAGE_ACCESS: 440 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access); 441 break; 442 case LIST_TYPE_STORAGE: 443 startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings); 444 break; 445 case LIST_TYPE_HIGH_POWER: 446 startAppInfoFragment(HighPowerDetail.class, R.string.high_power); 447 break; 448 // TODO: Figure out if there is a way where we can spin up the profile's settings 449 // process ahead of time, to avoid a long load of data when user clicks on a managed app. 450 // Maybe when they load the list of apps that contains managed profile apps. 451 default: 452 startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label); 453 break; 454 } 455 } 456 457 private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, int titleRes) { 458 AppInfoBase.startAppInfoFragment(fragment, titleRes, mCurrentPkgName, mCurrentUid, this, 459 INSTALLED_APP_DETAILS); 460 } 461 462 @Override 463 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 464 if (mListType == LIST_TYPE_DOMAINS_URLS) { 465 return; 466 } 467 HelpUtils.prepareHelpMenuItem(getActivity(), menu, mListType == LIST_TYPE_MAIN 468 ? R.string.help_uri_apps : R.string.help_uri_notifications); 469 mOptionsMenu = menu; 470 inflater.inflate(R.menu.manage_apps, menu); 471 updateOptionsMenu(); 472 } 473 474 @Override 475 public void onPrepareOptionsMenu(Menu menu) { 476 updateOptionsMenu(); 477 } 478 479 @Override 480 public void onDestroyOptionsMenu() { 481 mOptionsMenu = null; 482 } 483 484 void updateOptionsMenu() { 485 if (mOptionsMenu == null) { 486 return; 487 } 488 mOptionsMenu.findItem(R.id.advanced).setVisible(mListType == LIST_TYPE_MAIN); 489 490 mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE 491 && mSortOrder != R.id.sort_order_alpha); 492 mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE 493 && mSortOrder != R.id.sort_order_size); 494 495 mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem); 496 mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem); 497 } 498 499 @Override 500 public boolean onOptionsItemSelected(MenuItem item) { 501 int menuId = item.getItemId(); 502 switch(item.getItemId()) { 503 case R.id.sort_order_alpha: 504 case R.id.sort_order_size: 505 mSortOrder = menuId; 506 if (mApplications != null) { 507 mApplications.rebuild(mSortOrder); 508 } 509 break; 510 case R.id.show_system: 511 case R.id.hide_system: 512 mShowSystem = !mShowSystem; 513 mApplications.rebuild(false); 514 break; 515 case R.id.reset_app_preferences: 516 mResetAppsHelper.buildResetDialog(); 517 return true; 518 case R.id.advanced: 519 ((SettingsActivity) getActivity()).startPreferencePanel( 520 AdvancedAppSettings.class.getName(), null, R.string.advanced_apps, 521 null, this, ADVANCED_SETTINGS); 522 return true; 523 default: 524 // Handle the home button 525 return false; 526 } 527 updateOptionsMenu(); 528 return true; 529 } 530 531 @Override 532 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 533 if (mApplications != null && mApplications.getCount() > position) { 534 ApplicationsState.AppEntry entry = mApplications.getAppEntry(position); 535 mCurrentPkgName = entry.info.packageName; 536 mCurrentUid = entry.info.uid; 537 startApplicationDetailsActivity(); 538 } 539 } 540 541 @Override 542 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 543 mFilter = mFilterAdapter.getFilter(position); 544 mApplications.setFilter(mFilter); 545 if (DEBUG) Log.d(TAG, "Selecting filter " + mFilter); 546 } 547 548 @Override 549 public void onNothingSelected(AdapterView<?> parent) { 550 } 551 552 public void updateView() { 553 updateOptionsMenu(); 554 final Activity host = getActivity(); 555 if (host != null) { 556 host.invalidateOptionsMenu(); 557 } 558 } 559 560 public void setHasDisabled(boolean hasDisabledApps) { 561 mHasDisabledApps = hasDisabledApps; 562 mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps); 563 mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps); 564 } 565 566 static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> { 567 568 private final ManageApplications mManageApplications; 569 570 // Use ArrayAdapter for view logic, but have our own list for managing 571 // the options available. 572 private final ArrayList<Integer> mFilterOptions = new ArrayList<>(); 573 574 public FilterSpinnerAdapter(ManageApplications manageApplications) { 575 super(manageApplications.getActivity(), R.layout.filter_spinner_item); 576 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 577 mManageApplications = manageApplications; 578 } 579 580 public int getFilter(int position) { 581 return mFilterOptions.get(position); 582 } 583 584 public void setFilterEnabled(int filter, boolean enabled) { 585 if (enabled) { 586 enableFilter(filter); 587 } else { 588 disableFilter(filter); 589 } 590 } 591 592 public void enableFilter(int filter) { 593 if (mFilterOptions.contains(filter)) return; 594 if (DEBUG) Log.d(TAG, "Enabling filter " + filter); 595 mFilterOptions.add(filter); 596 Collections.sort(mFilterOptions); 597 mManageApplications.mSpinnerHeader.setVisibility( 598 mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE); 599 notifyDataSetChanged(); 600 if (mFilterOptions.size() == 1) { 601 if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter); 602 mManageApplications.mFilterSpinner.setSelection(0); 603 mManageApplications.onItemSelected(null, null, 0, 0); 604 } 605 } 606 607 public void disableFilter(int filter) { 608 if (!mFilterOptions.remove((Integer) filter)) { 609 return; 610 } 611 if (DEBUG) Log.d(TAG, "Disabling filter " + filter); 612 Collections.sort(mFilterOptions); 613 mManageApplications.mSpinnerHeader.setVisibility( 614 mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE); 615 notifyDataSetChanged(); 616 if (mManageApplications.mFilter == filter) { 617 if (mFilterOptions.size() > 0) { 618 if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0)); 619 mManageApplications.mFilterSpinner.setSelection(0); 620 mManageApplications.onItemSelected(null, null, 0, 0); 621 } 622 } 623 } 624 625 @Override 626 public int getCount() { 627 return mFilterOptions.size(); 628 } 629 630 @Override 631 public CharSequence getItem(int position) { 632 return getFilterString(mFilterOptions.get(position)); 633 } 634 635 private CharSequence getFilterString(int filter) { 636 return mManageApplications.getString(FILTER_LABELS[filter]); 637 } 638 639 } 640 641 /* 642 * Custom adapter implementation for the ListView 643 * This adapter maintains a map for each displayed application and its properties 644 * An index value on each AppInfo object indicates the correct position or index 645 * in the list. If the list gets updated dynamically when the user is viewing the list of 646 * applications, we need to return the correct index of position. This is done by mapping 647 * the getId methods via the package name into the internal maps and indices. 648 * The order of applications in the list is mirrored in mAppLocalList 649 */ 650 static class ApplicationsAdapter extends BaseAdapter implements Filterable, 651 ApplicationsState.Callbacks, AppStateBaseBridge.Callback, 652 AbsListView.RecyclerListener { 653 private final ApplicationsState mState; 654 private final ApplicationsState.Session mSession; 655 private final ManageApplications mManageApplications; 656 private final Context mContext; 657 private final ArrayList<View> mActive = new ArrayList<View>(); 658 private final AppStateBaseBridge mExtraInfoBridge; 659 private int mFilterMode; 660 private ArrayList<ApplicationsState.AppEntry> mBaseEntries; 661 private ArrayList<ApplicationsState.AppEntry> mEntries; 662 private boolean mResumed; 663 private int mLastSortMode=-1; 664 private int mWhichSize = SIZE_TOTAL; 665 CharSequence mCurFilterPrefix; 666 private PackageManager mPm; 667 private AppFilter mOverrideFilter; 668 669 private Filter mFilter = new Filter() { 670 @Override 671 protected FilterResults performFiltering(CharSequence constraint) { 672 ArrayList<ApplicationsState.AppEntry> entries 673 = applyPrefixFilter(constraint, mBaseEntries); 674 FilterResults fr = new FilterResults(); 675 fr.values = entries; 676 fr.count = entries.size(); 677 return fr; 678 } 679 680 @Override 681 @SuppressWarnings("unchecked") 682 protected void publishResults(CharSequence constraint, FilterResults results) { 683 mCurFilterPrefix = constraint; 684 mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values; 685 notifyDataSetChanged(); 686 } 687 }; 688 689 public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, 690 int filterMode) { 691 mState = state; 692 mSession = state.newSession(this); 693 mManageApplications = manageApplications; 694 mContext = manageApplications.getActivity(); 695 mPm = mContext.getPackageManager(); 696 mFilterMode = filterMode; 697 if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) { 698 mExtraInfoBridge = new AppStateNotificationBridge(mContext.getPackageManager(), 699 mState, this, manageApplications.mNotifBackend); 700 } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { 701 mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this); 702 } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) { 703 mExtraInfoBridge = new AppStatePowerBridge(mState, this); 704 } else { 705 mExtraInfoBridge = null; 706 } 707 } 708 709 public void setOverrideFilter(AppFilter overrideFilter) { 710 mOverrideFilter = overrideFilter; 711 rebuild(true); 712 } 713 714 public void setFilter(int filter) { 715 mFilterMode = filter; 716 rebuild(true); 717 } 718 719 public void resume(int sort) { 720 if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); 721 if (!mResumed) { 722 mResumed = true; 723 mSession.resume(); 724 mLastSortMode = sort; 725 if (mExtraInfoBridge != null) { 726 mExtraInfoBridge.resume(); 727 } 728 rebuild(true); 729 } else { 730 rebuild(sort); 731 } 732 } 733 734 public void pause() { 735 if (mResumed) { 736 mResumed = false; 737 mSession.pause(); 738 if (mExtraInfoBridge != null) { 739 mExtraInfoBridge.pause(); 740 } 741 } 742 } 743 744 public void release() { 745 mSession.release(); 746 if (mExtraInfoBridge != null) { 747 mExtraInfoBridge.release(); 748 } 749 } 750 751 public void rebuild(int sort) { 752 if (sort == mLastSortMode) { 753 return; 754 } 755 mLastSortMode = sort; 756 rebuild(true); 757 } 758 759 public void rebuild(boolean eraseold) { 760 if (DEBUG) Log.i(TAG, "Rebuilding app list..."); 761 ApplicationsState.AppFilter filterObj; 762 Comparator<AppEntry> comparatorObj; 763 boolean emulated = Environment.isExternalStorageEmulated(); 764 if (emulated) { 765 mWhichSize = SIZE_TOTAL; 766 } else { 767 mWhichSize = SIZE_INTERNAL; 768 } 769 filterObj = FILTERS[mFilterMode]; 770 if (mOverrideFilter != null) { 771 filterObj = mOverrideFilter; 772 } 773 if (!mManageApplications.mShowSystem) { 774 filterObj = new CompoundFilter(filterObj, 775 ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER); 776 } 777 switch (mLastSortMode) { 778 case R.id.sort_order_size: 779 switch (mWhichSize) { 780 case SIZE_INTERNAL: 781 comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR; 782 break; 783 case SIZE_EXTERNAL: 784 comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR; 785 break; 786 default: 787 comparatorObj = ApplicationsState.SIZE_COMPARATOR; 788 break; 789 } 790 break; 791 default: 792 comparatorObj = ApplicationsState.ALPHA_COMPARATOR; 793 break; 794 } 795 ArrayList<ApplicationsState.AppEntry> entries 796 = mSession.rebuild(filterObj, comparatorObj); 797 if (entries == null && !eraseold) { 798 // Don't have new list yet, but can continue using the old one. 799 return; 800 } 801 mBaseEntries = entries; 802 if (mBaseEntries != null) { 803 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 804 } else { 805 mEntries = null; 806 } 807 notifyDataSetChanged(); 808 809 if (mSession.getAllApps().size() != 0 810 && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) { 811 Utils.handleLoadingContainer(mManageApplications.mLoadingContainer, 812 mManageApplications.mListContainer, true, true); 813 } 814 if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { 815 // No enabled or disabled filters for usage access. 816 return; 817 } 818 819 mManageApplications.setHasDisabled(hasDisabledApps()); 820 } 821 822 private void updateLoading() { 823 Utils.handleLoadingContainer(mManageApplications.mLoadingContainer, 824 mManageApplications.mListContainer, mSession.getAllApps().size() != 0, false); 825 } 826 827 private boolean hasDisabledApps() { 828 ArrayList<AppEntry> allApps = mSession.getAllApps(); 829 for (int i = 0; i < allApps.size(); i++) { 830 if (!allApps.get(i).info.enabled) { 831 return true; 832 } 833 } 834 return false; 835 } 836 837 ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, 838 ArrayList<ApplicationsState.AppEntry> origEntries) { 839 if (prefix == null || prefix.length() == 0) { 840 return origEntries; 841 } else { 842 String prefixStr = ApplicationsState.normalize(prefix.toString()); 843 final String spacePrefixStr = " " + prefixStr; 844 ArrayList<ApplicationsState.AppEntry> newEntries 845 = new ArrayList<ApplicationsState.AppEntry>(); 846 for (int i=0; i<origEntries.size(); i++) { 847 ApplicationsState.AppEntry entry = origEntries.get(i); 848 String nlabel = entry.getNormalizedLabel(); 849 if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) { 850 newEntries.add(entry); 851 } 852 } 853 return newEntries; 854 } 855 } 856 857 @Override 858 public void onExtraInfoUpdated() { 859 rebuild(false); 860 } 861 862 @Override 863 public void onRunningStateChanged(boolean running) { 864 mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running); 865 } 866 867 @Override 868 public void onRebuildComplete(ArrayList<AppEntry> apps) { 869 if (mManageApplications.mLoadingContainer.getVisibility() == View.VISIBLE) { 870 mManageApplications.mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( 871 mContext, android.R.anim.fade_out)); 872 mManageApplications.mListContainer.startAnimation(AnimationUtils.loadAnimation( 873 mContext, android.R.anim.fade_in)); 874 } 875 mManageApplications.mListContainer.setVisibility(View.VISIBLE); 876 mManageApplications.mLoadingContainer.setVisibility(View.GONE); 877 mBaseEntries = apps; 878 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 879 notifyDataSetChanged(); 880 } 881 882 @Override 883 public void onPackageListChanged() { 884 rebuild(false); 885 } 886 887 @Override 888 public void onPackageIconChanged() { 889 // We ensure icons are loaded when their item is displayed, so 890 // don't care about icons loaded in the background. 891 } 892 893 @Override 894 public void onLoadEntriesCompleted() { 895 // No op. 896 } 897 898 @Override 899 public void onPackageSizeChanged(String packageName) { 900 for (int i=0; i<mActive.size(); i++) { 901 AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag(); 902 if (holder.entry.info.packageName.equals(packageName)) { 903 synchronized (holder.entry) { 904 updateSummary(holder); 905 } 906 if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName) 907 && mLastSortMode == R.id.sort_order_size) { 908 // We got the size information for the last app the 909 // user viewed, and are sorting by size... they may 910 // have cleared data, so we immediately want to resort 911 // the list with the new size to reflect it to the user. 912 rebuild(false); 913 } 914 return; 915 } 916 } 917 } 918 919 @Override 920 public void onLauncherInfoChanged() { 921 if (!mManageApplications.mShowSystem) { 922 rebuild(false); 923 } 924 } 925 926 @Override 927 public void onAllSizesComputed() { 928 if (mLastSortMode == R.id.sort_order_size) { 929 rebuild(false); 930 } 931 } 932 933 public int getCount() { 934 return mEntries != null ? mEntries.size() : 0; 935 } 936 937 public Object getItem(int position) { 938 return mEntries.get(position); 939 } 940 941 public ApplicationsState.AppEntry getAppEntry(int position) { 942 return mEntries.get(position); 943 } 944 945 public long getItemId(int position) { 946 return mEntries.get(position).id; 947 } 948 949 @Override 950 public boolean areAllItemsEnabled() { 951 return false; 952 } 953 954 public View getView(int position, View convertView, ViewGroup parent) { 955 // A ViewHolder keeps references to children views to avoid unnecessary calls 956 // to findViewById() on each row. 957 AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater, 958 convertView); 959 convertView = holder.rootView; 960 961 // Bind the data efficiently with the holder 962 ApplicationsState.AppEntry entry = mEntries.get(position); 963 synchronized (entry) { 964 holder.entry = entry; 965 if (entry.label != null) { 966 holder.appName.setText(entry.label); 967 } 968 mState.ensureIcon(entry); 969 if (entry.icon != null) { 970 holder.appIcon.setImageDrawable(entry.icon); 971 } 972 updateSummary(holder); 973 if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { 974 holder.disabled.setVisibility(View.VISIBLE); 975 holder.disabled.setText(R.string.not_installed); 976 } else if (!entry.info.enabled) { 977 holder.disabled.setVisibility(View.VISIBLE); 978 holder.disabled.setText(R.string.disabled); 979 } else { 980 holder.disabled.setVisibility(View.GONE); 981 } 982 holder.checkBox.setVisibility(View.GONE); 983 } 984 mActive.remove(convertView); 985 mActive.add(convertView); 986 return convertView; 987 } 988 989 private void updateSummary(AppViewHolder holder) { 990 switch (mManageApplications.mListType) { 991 case LIST_TYPE_NOTIFICATION: 992 if (holder.entry.extraInfo != null) { 993 holder.summary.setText(InstalledAppDetails.getNotificationSummary( 994 (AppRow) holder.entry.extraInfo, mContext)); 995 } else { 996 holder.summary.setText(null); 997 } 998 break; 999 1000 case LIST_TYPE_DOMAINS_URLS: 1001 holder.summary.setText(getDomainsSummary(holder.entry.info.packageName)); 1002 break; 1003 1004 case LIST_TYPE_USAGE_ACCESS: 1005 if (holder.entry.extraInfo != null) { 1006 holder.summary.setText(((UsageState) holder.entry.extraInfo).hasAccess() ? 1007 R.string.switch_on_text : R.string.switch_off_text); 1008 } else { 1009 holder.summary.setText(null); 1010 } 1011 break; 1012 1013 case LIST_TYPE_HIGH_POWER: 1014 holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry)); 1015 break; 1016 1017 default: 1018 holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize); 1019 break; 1020 } 1021 } 1022 1023 @Override 1024 public Filter getFilter() { 1025 return mFilter; 1026 } 1027 1028 @Override 1029 public void onMovedToScrapHeap(View view) { 1030 mActive.remove(view); 1031 } 1032 1033 private CharSequence getDomainsSummary(String packageName) { 1034 ArraySet<String> result = new ArraySet<>(); 1035 List<IntentFilterVerificationInfo> list = 1036 mPm.getIntentFilterVerifications(packageName); 1037 for (IntentFilterVerificationInfo ivi : list) { 1038 for (String host : ivi.getDomains()) { 1039 result.add(host); 1040 } 1041 } 1042 if (result.size() == 0) { 1043 return mContext.getString(R.string.domain_urls_summary_none); 1044 } else if (result.size() == 1) { 1045 return mContext.getString(R.string.domain_urls_summary_one, result.valueAt(0)); 1046 } else { 1047 return mContext.getString(R.string.domain_urls_summary_some, result.valueAt(0)); 1048 } 1049 } 1050 } 1051} 1052