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