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 17 package com.android.settings.applications; 18 19 import android.annotation.IdRes; 20 import android.annotation.Nullable; 21 import android.annotation.StringRes; 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageItemInfo; 27 import android.content.pm.PackageManager; 28 import android.icu.text.AlphabeticIndex; 29 import android.os.Bundle; 30 import android.os.Environment; 31 import android.os.Handler; 32 import android.os.LocaleList; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.preference.PreferenceFrameLayout; 36 import android.support.annotation.VisibleForTesting; 37 import android.text.TextUtils; 38 import android.util.ArraySet; 39 import android.util.Log; 40 import android.view.LayoutInflater; 41 import android.view.Menu; 42 import android.view.MenuInflater; 43 import android.view.MenuItem; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.widget.AbsListView; 47 import android.widget.AdapterView; 48 import android.widget.AdapterView.OnItemClickListener; 49 import android.widget.AdapterView.OnItemSelectedListener; 50 import android.widget.ArrayAdapter; 51 import android.widget.BaseAdapter; 52 import android.widget.Filter; 53 import android.widget.Filterable; 54 import android.widget.FrameLayout; 55 import android.widget.ListView; 56 import android.widget.SectionIndexer; 57 import android.widget.Spinner; 58 import android.widget.TextView; 59 60 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 61 import com.android.settings.R; 62 import com.android.settings.Settings; 63 import com.android.settings.Settings.AllApplicationsActivity; 64 import com.android.settings.Settings.GamesStorageActivity; 65 import com.android.settings.Settings.HighPowerApplicationsActivity; 66 import com.android.settings.Settings.ManageExternalSourcesActivity; 67 import com.android.settings.Settings.MoviesStorageActivity; 68 import com.android.settings.Settings.NotificationAppListActivity; 69 import com.android.settings.Settings.OverlaySettingsActivity; 70 import com.android.settings.Settings.StorageUseActivity; 71 import com.android.settings.Settings.UsageAccessSettingsActivity; 72 import com.android.settings.Settings.WriteSettingsActivity; 73 import com.android.settings.SettingsActivity; 74 import com.android.settings.Utils; 75 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState; 76 import com.android.settings.applications.AppStateUsageBridge.UsageState; 77 import com.android.settings.core.InstrumentedPreferenceFragment; 78 import com.android.settings.dashboard.SummaryLoader; 79 import com.android.settings.fuelgauge.HighPowerDetail; 80 import com.android.settings.fuelgauge.PowerWhitelistBackend; 81 import com.android.settings.notification.AppNotificationSettings; 82 import com.android.settings.notification.ConfigureNotificationSettings; 83 import com.android.settings.notification.NotificationBackend; 84 import com.android.settings.notification.NotificationBackend.AppRow; 85 import com.android.settings.widget.LoadingViewController; 86 import com.android.settingslib.HelpUtils; 87 import com.android.settingslib.applications.ApplicationsState; 88 import com.android.settingslib.applications.ApplicationsState.AppEntry; 89 import com.android.settingslib.applications.ApplicationsState.AppFilter; 90 import com.android.settingslib.applications.ApplicationsState.CompoundFilter; 91 import com.android.settingslib.applications.ApplicationsState.VolumeFilter; 92 import com.android.settingslib.applications.StorageStatsSource; 93 94 import java.util.ArrayList; 95 import java.util.Arrays; 96 import java.util.Collections; 97 import java.util.Comparator; 98 import java.util.Locale; 99 import java.util.Set; 100 101 /** 102 * Activity to pick an application that will be used to display installation information and 103 * options to uninstall/delete user data for system applications. This activity 104 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE 105 * intent. 106 */ 107 public class ManageApplications extends InstrumentedPreferenceFragment 108 implements OnItemClickListener, OnItemSelectedListener { 109 110 static final String TAG = "ManageApplications"; 111 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 112 113 // Intent extras. 114 public static final String EXTRA_CLASSNAME = "classname"; 115 // Used for storage only. 116 public static final String EXTRA_VOLUME_UUID = "volumeUuid"; 117 public static final String EXTRA_VOLUME_NAME = "volumeName"; 118 public static final String EXTRA_STORAGE_TYPE = "storageType"; 119 public static final String EXTRA_WORK_ONLY = "workProfileOnly"; 120 public static final String EXTRA_WORK_ID = "workId"; 121 122 private static final String EXTRA_SORT_ORDER = "sortOrder"; 123 private static final String EXTRA_SHOW_SYSTEM = "showSystem"; 124 private static final String EXTRA_HAS_ENTRIES = "hasEntries"; 125 private static final String EXTRA_HAS_BRIDGE = "hasBridge"; 126 127 // attributes used as keys when passing values to InstalledAppDetails activity 128 public static final String APP_CHG = "chg"; 129 130 // constant value that can be used to check return code from sub activity. 131 private static final int INSTALLED_APP_DETAILS = 1; 132 private static final int ADVANCED_SETTINGS = 2; 133 134 public static final int SIZE_TOTAL = 0; 135 public static final int SIZE_INTERNAL = 1; 136 public static final int SIZE_EXTERNAL = 2; 137 138 // Filter options used for displayed list of applications 139 // Filters will appear sorted based on their value defined here. 140 public static final int FILTER_APPS_POWER_WHITELIST = 0; 141 public static final int FILTER_APPS_POWER_WHITELIST_ALL = 1; 142 public static final int FILTER_APPS_ALL = 2; 143 public static final int FILTER_APPS_ENABLED = 3; 144 public static final int FILTER_APPS_INSTANT = 4; 145 public static final int FILTER_APPS_DISABLED = 5; 146 public static final int FILTER_APPS_BLOCKED = 6; 147 public static final int FILTER_APPS_PERSONAL = 7; 148 public static final int FILTER_APPS_WORK = 8; 149 public static final int FILTER_APPS_USAGE_ACCESS = 9; 150 public static final int FILTER_APPS_WITH_OVERLAY = 10; 151 public static final int FILTER_APPS_WRITE_SETTINGS = 11; 152 public static final int FILTER_APPS_INSTALL_SOURCES = 12; 153 public static final int FILTER_APPS_COUNT = 13; // This should always be the last entry 154 155 // Mapping to string labels for the FILTER_APPS_* constants above. 156 @IdRes 157 public static final int[] FILTER_LABELS = new int[FILTER_APPS_COUNT]; 158 159 // Mapping to filters for the FILTER_APPS_* constants above. 160 public static final AppFilter[] FILTERS = new AppFilter[FILTER_APPS_COUNT]; 161 162 static { 163 // High power whitelist, on 164 FILTER_LABELS[FILTER_APPS_POWER_WHITELIST] = R.string.high_power_filter_on; 165 FILTERS[FILTER_APPS_POWER_WHITELIST] = new CompoundFilter( 166 AppStatePowerBridge.FILTER_POWER_WHITELISTED, 167 ApplicationsState.FILTER_ALL_ENABLED); 168 169 // Without disabled until used 170 FILTER_LABELS[FILTER_APPS_POWER_WHITELIST_ALL] = R.string.filter_all_apps; 171 FILTERS[FILTER_APPS_POWER_WHITELIST_ALL] = new CompoundFilter( 172 ApplicationsState.FILTER_WITHOUT_DISABLED_UNTIL_USED, 173 ApplicationsState.FILTER_ALL_ENABLED); 174 175 // All apps 176 FILTER_LABELS[FILTER_APPS_ALL] = R.string.filter_all_apps; 177 FILTERS[FILTER_APPS_ALL] = ApplicationsState.FILTER_EVERYTHING; 178 179 // Enabled 180 FILTER_LABELS[FILTER_APPS_ENABLED] = R.string.filter_enabled_apps; 181 FILTERS[FILTER_APPS_ENABLED] = ApplicationsState.FILTER_ALL_ENABLED; 182 183 // Disabled 184 FILTER_LABELS[FILTER_APPS_DISABLED] = R.string.filter_apps_disabled; 185 FILTERS[FILTER_APPS_DISABLED] = ApplicationsState.FILTER_DISABLED; 186 187 // Instant 188 FILTER_LABELS[FILTER_APPS_INSTANT] = R.string.filter_instant_apps; 189 FILTERS[FILTER_APPS_INSTANT] = ApplicationsState.FILTER_INSTANT; 190 191 // Blocked Notifications 192 FILTER_LABELS[FILTER_APPS_BLOCKED] = R.string.filter_notif_blocked_apps; 193 FILTERS[FILTER_APPS_BLOCKED] = AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED; 194 195 // Personal 196 FILTER_LABELS[FILTER_APPS_PERSONAL] = R.string.filter_personal_apps; 197 FILTERS[FILTER_APPS_PERSONAL] = ApplicationsState.FILTER_PERSONAL; 198 199 // Work 200 FILTER_LABELS[FILTER_APPS_WORK] = R.string.filter_work_apps; 201 FILTERS[FILTER_APPS_WORK] = ApplicationsState.FILTER_WORK; 202 203 // Usage access screen, never displayed. 204 FILTER_LABELS[FILTER_APPS_USAGE_ACCESS] = R.string.filter_all_apps; 205 FILTERS[FILTER_APPS_USAGE_ACCESS] = AppStateUsageBridge.FILTER_APP_USAGE; 206 207 // Apps that can draw overlays 208 FILTER_LABELS[FILTER_APPS_WITH_OVERLAY] = R.string.filter_overlay_apps; 209 FILTERS[FILTER_APPS_WITH_OVERLAY] = AppStateOverlayBridge.FILTER_SYSTEM_ALERT_WINDOW; 210 211 // Apps that can write system settings 212 FILTER_LABELS[FILTER_APPS_WRITE_SETTINGS] = R.string.filter_write_settings_apps; 213 FILTERS[FILTER_APPS_WRITE_SETTINGS] = AppStateWriteSettingsBridge.FILTER_WRITE_SETTINGS; 214 215 // Apps that are trusted sources of apks 216 FILTER_LABELS[FILTER_APPS_INSTALL_SOURCES] = R.string.filter_install_sources_apps; 217 FILTERS[FILTER_APPS_INSTALL_SOURCES] = AppStateInstallAppsBridge.FILTER_APP_SOURCES; 218 } 219 220 // Storage types. Used to determine what the extra item in the list of preferences is. 221 public static final int STORAGE_TYPE_DEFAULT = 0; // Show all apps that are not categorized. 222 public static final int STORAGE_TYPE_MUSIC = 1; 223 public static final int STORAGE_TYPE_LEGACY = 2; // Show apps even if they can be categorized. 224 public static final int STORAGE_TYPE_PHOTOS_VIDEOS = 3; 225 226 private static final int NO_USER_SPECIFIED = -1; 227 228 // sort order 229 private int mSortOrder = R.id.sort_order_alpha; 230 231 // whether showing system apps. 232 private boolean mShowSystem; 233 234 private ApplicationsState mApplicationsState; 235 236 public int mListType; 237 public int mFilter; 238 239 public ApplicationsAdapter mApplications; 240 241 private View mLoadingContainer; 242 243 private View mListContainer; 244 245 // ListView used to display list 246 private ListView mListView; 247 248 // Size resource used for packages whose size computation failed for some reason 249 CharSequence mInvalidSizeStr; 250 251 // layout inflater object used to inflate views 252 private LayoutInflater mInflater; 253 254 private String mCurrentPkgName; 255 private int mCurrentUid; 256 private boolean mFinishAfterDialog; 257 258 private Menu mOptionsMenu; 259 260 public static final int LIST_TYPE_MAIN = 0; 261 public static final int LIST_TYPE_NOTIFICATION = 1; 262 public static final int LIST_TYPE_STORAGE = 3; 263 public static final int LIST_TYPE_USAGE_ACCESS = 4; 264 public static final int LIST_TYPE_HIGH_POWER = 5; 265 public static final int LIST_TYPE_OVERLAY = 6; 266 public static final int LIST_TYPE_WRITE_SETTINGS = 7; 267 public static final int LIST_TYPE_MANAGE_SOURCES = 8; 268 public static final int LIST_TYPE_GAMES = 9; 269 public static final int LIST_TYPE_MOVIES = 10; 270 public static final int LIST_TYPE_PHOTOGRAPHY = 11; 271 272 273 // List types that should show instant apps. 274 public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( 275 LIST_TYPE_MAIN, 276 LIST_TYPE_STORAGE)); 277 278 private View mRootView; 279 280 private View mSpinnerHeader; 281 private Spinner mFilterSpinner; 282 private FilterSpinnerAdapter mFilterAdapter; 283 private NotificationBackend mNotifBackend; 284 private ResetAppsHelper mResetAppsHelper; 285 private String mVolumeUuid; 286 private int mStorageType; 287 private boolean mIsWorkOnly; 288 private int mWorkUserId; 289 290 @Override 291 public void onCreate(Bundle savedInstanceState) { 292 super.onCreate(savedInstanceState); 293 setHasOptionsMenu(true); 294 mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); 295 296 Intent intent = getActivity().getIntent(); 297 Bundle args = getArguments(); 298 String className = args != null ? args.getString(EXTRA_CLASSNAME) : null; 299 if (className == null) { 300 className = intent.getComponent().getClassName(); 301 } 302 if (className.equals(AllApplicationsActivity.class.getName())) { 303 mShowSystem = true; 304 } else if (className.equals(NotificationAppListActivity.class.getName()) 305 || this instanceof NotificationApps) { 306 mListType = LIST_TYPE_NOTIFICATION; 307 mNotifBackend = new NotificationBackend(); 308 } else if (className.equals(StorageUseActivity.class.getName())) { 309 if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) { 310 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID); 311 mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT); 312 mListType = LIST_TYPE_STORAGE; 313 } else { 314 // No volume selected, display a normal list, sorted by size. 315 mListType = LIST_TYPE_MAIN; 316 } 317 mSortOrder = R.id.sort_order_size; 318 } else if (className.equals(UsageAccessSettingsActivity.class.getName())) { 319 mListType = LIST_TYPE_USAGE_ACCESS; 320 } else if (className.equals(HighPowerApplicationsActivity.class.getName())) { 321 mListType = LIST_TYPE_HIGH_POWER; 322 // Default to showing system. 323 mShowSystem = true; 324 } else if (className.equals(OverlaySettingsActivity.class.getName())) { 325 mListType = LIST_TYPE_OVERLAY; 326 } else if (className.equals(WriteSettingsActivity.class.getName())) { 327 mListType = LIST_TYPE_WRITE_SETTINGS; 328 } else if (className.equals(ManageExternalSourcesActivity.class.getName())) { 329 mListType = LIST_TYPE_MANAGE_SOURCES; 330 } else if (className.equals(GamesStorageActivity.class.getName())) { 331 mListType = LIST_TYPE_GAMES; 332 mSortOrder = R.id.sort_order_size; 333 } else if (className.equals(MoviesStorageActivity.class.getName())) { 334 mListType = LIST_TYPE_MOVIES; 335 mSortOrder = R.id.sort_order_size; 336 } else if (className.equals(Settings.PhotosStorageActivity.class.getName())) { 337 mListType = LIST_TYPE_PHOTOGRAPHY; 338 mSortOrder = R.id.sort_order_size; 339 mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT); 340 } else { 341 mListType = LIST_TYPE_MAIN; 342 } 343 mFilter = getDefaultFilter(); 344 mIsWorkOnly = args != null ? args.getBoolean(EXTRA_WORK_ONLY) : false; 345 mWorkUserId = args != null ? args.getInt(EXTRA_WORK_ID) : NO_USER_SPECIFIED; 346 347 if (savedInstanceState != null) { 348 mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder); 349 mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); 350 } 351 352 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 353 354 mResetAppsHelper = new ResetAppsHelper(getActivity()); 355 } 356 357 358 @Override 359 public View onCreateView(LayoutInflater inflater, ViewGroup container, 360 Bundle savedInstanceState) { 361 // initialize the inflater 362 mInflater = inflater; 363 364 mRootView = inflater.inflate(R.layout.manage_applications_apps, null); 365 mLoadingContainer = mRootView.findViewById(R.id.loading_container); 366 mListContainer = mRootView.findViewById(R.id.list_container); 367 if (mListContainer != null) { 368 // Create adapter and list view here 369 View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); 370 ListView lv = (ListView) mListContainer.findViewById(android.R.id.list); 371 if (emptyView != null) { 372 lv.setEmptyView(emptyView); 373 } 374 lv.setOnItemClickListener(this); 375 lv.setSaveEnabled(true); 376 lv.setItemsCanFocus(true); 377 lv.setTextFilterEnabled(true); 378 mListView = lv; 379 mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter); 380 if (savedInstanceState != null) { 381 mApplications.mHasReceivedLoadEntries = 382 savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); 383 mApplications.mHasReceivedBridgeCallback = 384 savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false); 385 } 386 int userId = mIsWorkOnly ? mWorkUserId : UserHandle.getUserId(mCurrentUid); 387 if (mStorageType == STORAGE_TYPE_MUSIC) { 388 Context context = getContext(); 389 mApplications.setExtraViewController( 390 new MusicViewHolderController( 391 context, 392 new StorageStatsSource(context), 393 mVolumeUuid, 394 UserHandle.of(userId))); 395 } else if (mStorageType == STORAGE_TYPE_PHOTOS_VIDEOS) { 396 Context context = getContext(); 397 mApplications.setExtraViewController( 398 new PhotosViewHolderController( 399 context, 400 new StorageStatsSource(context), 401 mVolumeUuid, 402 UserHandle.of(userId))); 403 } 404 mListView.setAdapter(mApplications); 405 mListView.setRecyclerListener(mApplications); 406 mListView.setFastScrollEnabled(isFastScrollEnabled()); 407 408 Utils.prepareCustomPreferencesList(container, mRootView, mListView, false); 409 } 410 411 // We have to do this now because PreferenceFrameLayout looks at it 412 // only when the view is added. 413 if (container instanceof PreferenceFrameLayout) { 414 ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true; 415 } 416 417 createHeader(); 418 419 mResetAppsHelper.onRestoreInstanceState(savedInstanceState); 420 421 return mRootView; 422 } 423 424 @VisibleForTesting 425 void createHeader() { 426 Activity activity = getActivity(); 427 FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header); 428 mSpinnerHeader = activity.getLayoutInflater() 429 .inflate(R.layout.apps_filter_spinner, pinnedHeader, false); 430 mFilterSpinner = (Spinner) mSpinnerHeader.findViewById(R.id.filter_spinner); 431 mFilterAdapter = new FilterSpinnerAdapter(this); 432 mFilterSpinner.setAdapter(mFilterAdapter); 433 mFilterSpinner.setOnItemSelectedListener(this); 434 pinnedHeader.addView(mSpinnerHeader, 0); 435 436 mFilterAdapter.enableFilter(getDefaultFilter()); 437 if (mListType == LIST_TYPE_MAIN) { 438 if (UserManager.get(getActivity()).getUserProfiles().size() > 1) { 439 mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL); 440 mFilterAdapter.enableFilter(FILTER_APPS_WORK); 441 } 442 } 443 if (mListType == LIST_TYPE_NOTIFICATION) { 444 mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED); 445 } 446 if (mListType == LIST_TYPE_HIGH_POWER) { 447 mFilterAdapter.enableFilter(FILTER_APPS_POWER_WHITELIST_ALL); 448 } 449 450 AppFilter compositeFilter = getCompositeFilter(mListType, mStorageType, mVolumeUuid); 451 if (mIsWorkOnly) { 452 compositeFilter = new CompoundFilter(compositeFilter, FILTERS[FILTER_APPS_WORK]); 453 } 454 if (compositeFilter != null) { 455 mApplications.setCompositeFilter(compositeFilter); 456 } 457 } 458 459 @VisibleForTesting 460 @Nullable 461 static AppFilter getCompositeFilter(int listType, int storageType, String volumeUuid) { 462 AppFilter filter = new VolumeFilter(volumeUuid); 463 if (listType == LIST_TYPE_STORAGE) { 464 if (storageType == STORAGE_TYPE_MUSIC) { 465 filter = new CompoundFilter(ApplicationsState.FILTER_AUDIO, filter); 466 } else if (storageType == STORAGE_TYPE_DEFAULT) { 467 filter = new CompoundFilter(ApplicationsState.FILTER_OTHER_APPS, filter); 468 } 469 return filter; 470 } 471 if (listType == LIST_TYPE_GAMES) { 472 return new CompoundFilter(ApplicationsState.FILTER_GAMES, filter); 473 } else if (listType == LIST_TYPE_MOVIES) { 474 return new CompoundFilter(ApplicationsState.FILTER_MOVIES, filter); 475 } else if (listType == LIST_TYPE_PHOTOGRAPHY) { 476 return new CompoundFilter(ApplicationsState.FILTER_PHOTOS, filter); 477 } 478 479 return null; 480 } 481 482 private int getDefaultFilter() { 483 switch (mListType) { 484 case LIST_TYPE_USAGE_ACCESS: 485 return FILTER_APPS_USAGE_ACCESS; 486 case LIST_TYPE_HIGH_POWER: 487 return FILTER_APPS_POWER_WHITELIST; 488 case LIST_TYPE_OVERLAY: 489 return FILTER_APPS_WITH_OVERLAY; 490 case LIST_TYPE_WRITE_SETTINGS: 491 return FILTER_APPS_WRITE_SETTINGS; 492 case LIST_TYPE_MANAGE_SOURCES: 493 return FILTER_APPS_INSTALL_SOURCES; 494 default: 495 return FILTER_APPS_ALL; 496 } 497 } 498 499 private boolean isFastScrollEnabled() { 500 switch (mListType) { 501 case LIST_TYPE_MAIN: 502 case LIST_TYPE_NOTIFICATION: 503 case LIST_TYPE_STORAGE: 504 case LIST_TYPE_GAMES: 505 case LIST_TYPE_MOVIES: 506 case LIST_TYPE_PHOTOGRAPHY: 507 return mSortOrder == R.id.sort_order_alpha; 508 default: 509 return false; 510 } 511 } 512 513 @Override 514 public int getMetricsCategory() { 515 switch (mListType) { 516 case LIST_TYPE_MAIN: 517 return MetricsEvent.MANAGE_APPLICATIONS; 518 case LIST_TYPE_NOTIFICATION: 519 return MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS; 520 case LIST_TYPE_STORAGE: 521 if (mStorageType == STORAGE_TYPE_MUSIC) { 522 return MetricsEvent.APPLICATIONS_STORAGE_MUSIC; 523 } 524 return MetricsEvent.APPLICATIONS_STORAGE_APPS; 525 case LIST_TYPE_GAMES: 526 return MetricsEvent.APPLICATIONS_STORAGE_GAMES; 527 case LIST_TYPE_MOVIES: 528 return MetricsEvent.APPLICATIONS_STORAGE_MOVIES; 529 case LIST_TYPE_PHOTOGRAPHY: 530 return MetricsEvent.APPLICATIONS_STORAGE_PHOTOS; 531 case LIST_TYPE_USAGE_ACCESS: 532 return MetricsEvent.USAGE_ACCESS; 533 case LIST_TYPE_HIGH_POWER: 534 return MetricsEvent.APPLICATIONS_HIGH_POWER_APPS; 535 case LIST_TYPE_OVERLAY: 536 return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS; 537 case LIST_TYPE_WRITE_SETTINGS: 538 return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS; 539 case LIST_TYPE_MANAGE_SOURCES: 540 return MetricsEvent.MANAGE_EXTERNAL_SOURCES; 541 default: 542 return MetricsEvent.VIEW_UNKNOWN; 543 } 544 } 545 546 @Override 547 public void onStart() { 548 super.onStart(); 549 updateView(); 550 if (mApplications != null) { 551 mApplications.resume(mSortOrder); 552 mApplications.updateLoading(); 553 } 554 } 555 556 @Override 557 public void onSaveInstanceState(Bundle outState) { 558 super.onSaveInstanceState(outState); 559 mResetAppsHelper.onSaveInstanceState(outState); 560 outState.putInt(EXTRA_SORT_ORDER, mSortOrder); 561 outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem); 562 outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries); 563 outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback); 564 } 565 566 @Override 567 public void onStop() { 568 super.onStop(); 569 if (mApplications != null) { 570 mApplications.pause(); 571 } 572 mResetAppsHelper.stop(); 573 } 574 575 @Override 576 public void onDestroyView() { 577 super.onDestroyView(); 578 579 if (mApplications != null) { 580 mApplications.release(); 581 } 582 mRootView = null; 583 } 584 585 @Override 586 public void onActivityResult(int requestCode, int resultCode, Intent data) { 587 if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { 588 if (mListType == LIST_TYPE_NOTIFICATION) { 589 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid); 590 } else if (mListType == LIST_TYPE_HIGH_POWER || mListType == LIST_TYPE_OVERLAY 591 || mListType == LIST_TYPE_WRITE_SETTINGS) { 592 if (mFinishAfterDialog) { 593 getActivity().onBackPressed(); 594 } else { 595 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid); 596 } 597 } else { 598 mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid)); 599 } 600 } 601 } 602 603 // utility method used to start sub activity 604 private void startApplicationDetailsActivity() { 605 switch (mListType) { 606 case LIST_TYPE_NOTIFICATION: 607 startAppInfoFragment(AppNotificationSettings.class, R.string.notifications_title); 608 break; 609 case LIST_TYPE_USAGE_ACCESS: 610 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access); 611 break; 612 case LIST_TYPE_STORAGE: 613 startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings); 614 break; 615 case LIST_TYPE_HIGH_POWER: 616 HighPowerDetail.show(this, mCurrentPkgName, INSTALLED_APP_DETAILS, 617 mFinishAfterDialog); 618 break; 619 case LIST_TYPE_OVERLAY: 620 startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings); 621 break; 622 case LIST_TYPE_WRITE_SETTINGS: 623 startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings); 624 break; 625 case LIST_TYPE_MANAGE_SOURCES: 626 startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps); 627 break; 628 case LIST_TYPE_GAMES: 629 startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings); 630 break; 631 case LIST_TYPE_MOVIES: 632 startAppInfoFragment(AppStorageSettings.class, R.string.storage_movies_tv); 633 break; 634 case LIST_TYPE_PHOTOGRAPHY: 635 startAppInfoFragment(AppStorageSettings.class, R.string.storage_photos_videos); 636 break; 637 // TODO: Figure out if there is a way where we can spin up the profile's settings 638 // process ahead of time, to avoid a long load of data when user clicks on a managed 639 // app. Maybe when they load the list of apps that contains managed profile apps. 640 default: 641 startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label); 642 break; 643 } 644 } 645 646 private void startAppInfoFragment(Class<?> fragment, int titleRes) { 647 AppInfoBase.startAppInfoFragment(fragment, titleRes, mCurrentPkgName, mCurrentUid, this, 648 INSTALLED_APP_DETAILS, getMetricsCategory()); 649 } 650 651 @Override 652 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 653 HelpUtils.prepareHelpMenuItem(getActivity(), menu, getHelpResource(), getClass().getName()); 654 mOptionsMenu = menu; 655 inflater.inflate(R.menu.manage_apps, menu); 656 updateOptionsMenu(); 657 } 658 659 @Override 660 public void onPrepareOptionsMenu(Menu menu) { 661 updateOptionsMenu(); 662 } 663 664 @Override 665 public void onDestroyOptionsMenu() { 666 mOptionsMenu = null; 667 } 668 669 @StringRes 670 int getHelpResource() { 671 if (mListType == LIST_TYPE_MAIN) { 672 return R.string.help_uri_apps; 673 } else if (mListType == LIST_TYPE_USAGE_ACCESS) { 674 return R.string.help_url_usage_access; 675 } else { 676 return R.string.help_uri_notifications; 677 } 678 } 679 680 void updateOptionsMenu() { 681 if (mOptionsMenu == null) { 682 return; 683 } 684 mOptionsMenu.findItem(R.id.advanced).setVisible(false); 685 686 mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE 687 && mSortOrder != R.id.sort_order_alpha); 688 mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE 689 && mSortOrder != R.id.sort_order_size); 690 691 mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem 692 && mListType != LIST_TYPE_HIGH_POWER); 693 mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem 694 && mListType != LIST_TYPE_HIGH_POWER); 695 696 mOptionsMenu.findItem(R.id.reset_app_preferences).setVisible(mListType == LIST_TYPE_MAIN); 697 } 698 699 @Override 700 public boolean onOptionsItemSelected(MenuItem item) { 701 int menuId = item.getItemId(); 702 switch (item.getItemId()) { 703 case R.id.sort_order_alpha: 704 case R.id.sort_order_size: 705 mSortOrder = menuId; 706 mListView.setFastScrollEnabled(isFastScrollEnabled()); 707 if (mApplications != null) { 708 mApplications.rebuild(mSortOrder); 709 } 710 break; 711 case R.id.show_system: 712 case R.id.hide_system: 713 mShowSystem = !mShowSystem; 714 mApplications.rebuild(false); 715 break; 716 case R.id.reset_app_preferences: 717 mResetAppsHelper.buildResetDialog(); 718 return true; 719 case R.id.advanced: 720 if (mListType == LIST_TYPE_NOTIFICATION) { 721 ((SettingsActivity) getActivity()).startPreferencePanel(this, 722 ConfigureNotificationSettings.class.getName(), null, 723 R.string.configure_notification_settings, null, this, 724 ADVANCED_SETTINGS); 725 } else { 726 ((SettingsActivity) getActivity()).startPreferencePanel(this, 727 DefaultAppSettings.class.getName(), null, R.string.configure_apps, 728 null, this, ADVANCED_SETTINGS); 729 } 730 return true; 731 default: 732 // Handle the home button 733 return false; 734 } 735 updateOptionsMenu(); 736 return true; 737 } 738 739 @Override 740 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 741 if (mApplications == null) { 742 return; 743 } 744 745 if (mApplications.getApplicationCount() > position) { 746 ApplicationsState.AppEntry entry = mApplications.getAppEntry(position); 747 mCurrentPkgName = entry.info.packageName; 748 mCurrentUid = entry.info.uid; 749 startApplicationDetailsActivity(); 750 } else { 751 mApplications.mExtraViewController.onClick(this); 752 } 753 } 754 755 @Override 756 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 757 mFilter = mFilterAdapter.getFilter(position); 758 mApplications.setFilter(mFilter); 759 if (DEBUG) Log.d(TAG, "Selecting filter " + mFilter); 760 } 761 762 @Override 763 public void onNothingSelected(AdapterView<?> parent) { 764 } 765 766 public void updateView() { 767 updateOptionsMenu(); 768 final Activity host = getActivity(); 769 if (host != null) { 770 host.invalidateOptionsMenu(); 771 } 772 } 773 774 public void setHasDisabled(boolean hasDisabledApps) { 775 if (mListType != LIST_TYPE_MAIN) { 776 return; 777 } 778 mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps); 779 mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps); 780 } 781 782 public void setHasInstant(boolean haveInstantApps) { 783 if (LIST_TYPES_WITH_INSTANT.contains(mListType)) { 784 mFilterAdapter.setFilterEnabled(FILTER_APPS_INSTANT, haveInstantApps); 785 } 786 } 787 788 static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> { 789 790 private final ManageApplications mManageApplications; 791 792 // Use ArrayAdapter for view logic, but have our own list for managing 793 // the options available. 794 private final ArrayList<Integer> mFilterOptions = new ArrayList<>(); 795 796 public FilterSpinnerAdapter(ManageApplications manageApplications) { 797 super(manageApplications.mFilterSpinner.getContext(), R.layout.filter_spinner_item); 798 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 799 mManageApplications = manageApplications; 800 } 801 802 public int getFilter(int position) { 803 return mFilterOptions.get(position); 804 } 805 806 public void setFilterEnabled(int filter, boolean enabled) { 807 if (enabled) { 808 enableFilter(filter); 809 } else { 810 disableFilter(filter); 811 } 812 } 813 814 public void enableFilter(int filter) { 815 if (mFilterOptions.contains(filter)) return; 816 if (DEBUG) Log.d(TAG, "Enabling filter " + filter); 817 mFilterOptions.add(filter); 818 Collections.sort(mFilterOptions); 819 mManageApplications.mSpinnerHeader.setVisibility( 820 mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE); 821 notifyDataSetChanged(); 822 if (mFilterOptions.size() == 1) { 823 if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter); 824 mManageApplications.mFilterSpinner.setSelection(0); 825 mManageApplications.onItemSelected(null, null, 0, 0); 826 } 827 } 828 829 public void disableFilter(int filter) { 830 if (!mFilterOptions.remove((Integer) filter)) { 831 return; 832 } 833 if (DEBUG) Log.d(TAG, "Disabling filter " + filter); 834 Collections.sort(mFilterOptions); 835 mManageApplications.mSpinnerHeader.setVisibility( 836 mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE); 837 notifyDataSetChanged(); 838 if (mManageApplications.mFilter == filter) { 839 if (mFilterOptions.size() > 0) { 840 if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0)); 841 mManageApplications.mFilterSpinner.setSelection(0); 842 mManageApplications.onItemSelected(null, null, 0, 0); 843 } 844 } 845 } 846 847 @Override 848 public int getCount() { 849 return mFilterOptions.size(); 850 } 851 852 @Override 853 public CharSequence getItem(int position) { 854 return getFilterString(mFilterOptions.get(position)); 855 } 856 857 private CharSequence getFilterString(int filter) { 858 return mManageApplications.getString(FILTER_LABELS[filter]); 859 } 860 861 } 862 863 /* 864 * Custom adapter implementation for the ListView 865 * This adapter maintains a map for each displayed application and its properties 866 * An index value on each AppInfo object indicates the correct position or index 867 * in the list. If the list gets updated dynamically when the user is viewing the list of 868 * applications, we need to return the correct index of position. This is done by mapping 869 * the getId methods via the package name into the internal maps and indices. 870 * The order of applications in the list is mirrored in mAppLocalList 871 */ 872 static class ApplicationsAdapter extends BaseAdapter implements Filterable, 873 ApplicationsState.Callbacks, AppStateBaseBridge.Callback, 874 AbsListView.RecyclerListener, SectionIndexer { 875 876 // how long to wait for app list to populate without showing the loading container 877 private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L; 878 879 private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0]; 880 881 private final ApplicationsState mState; 882 private final ApplicationsState.Session mSession; 883 private final ManageApplications mManageApplications; 884 private final Context mContext; 885 private final ArrayList<View> mActive = new ArrayList<View>(); 886 private final AppStateBaseBridge mExtraInfoBridge; 887 private final Handler mBgHandler; 888 private final Handler mFgHandler; 889 private final LoadingViewController mLoadingViewController; 890 891 private int mFilterMode; 892 private ArrayList<ApplicationsState.AppEntry> mBaseEntries; 893 private ArrayList<ApplicationsState.AppEntry> mEntries; 894 private boolean mResumed; 895 private int mLastSortMode = -1; 896 private int mWhichSize = SIZE_TOTAL; 897 CharSequence mCurFilterPrefix; 898 private PackageManager mPm; 899 private AppFilter mCompositeFilter; 900 private boolean mHasReceivedLoadEntries; 901 private boolean mHasReceivedBridgeCallback; 902 private FileViewHolderController mExtraViewController; 903 904 // These two variables are used to remember and restore the last scroll position when this 905 // fragment is paused. We need this special handling because app entries are added gradually 906 // when we rebuild the list after the user made some changes, like uninstalling an app. 907 private int mLastIndex = -1; 908 private int mLastTop; 909 910 private AlphabeticIndex.ImmutableIndex<Locale> mIndex; 911 private SectionInfo[] mSections = EMPTY_SECTIONS; 912 private int[] mPositionToSectionIndex; 913 914 private Filter mFilter = new Filter() { 915 @Override 916 protected FilterResults performFiltering(CharSequence constraint) { 917 ArrayList<ApplicationsState.AppEntry> entries 918 = applyPrefixFilter(constraint, mBaseEntries); 919 FilterResults fr = new FilterResults(); 920 fr.values = entries; 921 fr.count = entries.size(); 922 return fr; 923 } 924 925 @Override 926 @SuppressWarnings("unchecked") 927 protected void publishResults(CharSequence constraint, FilterResults results) { 928 mCurFilterPrefix = constraint; 929 mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values; 930 rebuildSections(); 931 notifyDataSetChanged(); 932 } 933 }; 934 935 936 public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, 937 int filterMode) { 938 mState = state; 939 mFgHandler = new Handler(); 940 mBgHandler = new Handler(mState.getBackgroundLooper()); 941 mSession = state.newSession(this); 942 mManageApplications = manageApplications; 943 mLoadingViewController = new LoadingViewController( 944 mManageApplications.mLoadingContainer, 945 mManageApplications.mListContainer 946 ); 947 mContext = manageApplications.getActivity(); 948 mPm = mContext.getPackageManager(); 949 mFilterMode = filterMode; 950 if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) { 951 mExtraInfoBridge = new AppStateNotificationBridge(mContext, mState, this, 952 manageApplications.mNotifBackend); 953 } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { 954 mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this); 955 } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) { 956 mExtraInfoBridge = new AppStatePowerBridge(mState, this); 957 } else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) { 958 mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this); 959 } else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) { 960 mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this); 961 } else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) { 962 mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this); 963 } else { 964 mExtraInfoBridge = null; 965 } 966 } 967 968 public void setCompositeFilter(AppFilter compositeFilter) { 969 mCompositeFilter = compositeFilter; 970 rebuild(true); 971 } 972 973 public void setFilter(int filter) { 974 mFilterMode = filter; 975 rebuild(true); 976 } 977 978 public void setExtraViewController(FileViewHolderController extraViewController) { 979 mExtraViewController = extraViewController; 980 mBgHandler.post(() -> { 981 mExtraViewController.queryStats(); 982 mFgHandler.post(() -> { 983 onExtraViewCompleted(); 984 }); 985 }); 986 } 987 988 public void resume(int sort) { 989 if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); 990 if (!mResumed) { 991 mResumed = true; 992 mSession.resume(); 993 mLastSortMode = sort; 994 if (mExtraInfoBridge != null) { 995 mExtraInfoBridge.resume(); 996 } 997 rebuild(false); 998 } else { 999 rebuild(sort); 1000 } 1001 } 1002 1003 public void pause() { 1004 if (mResumed) { 1005 mResumed = false; 1006 mSession.pause(); 1007 if (mExtraInfoBridge != null) { 1008 mExtraInfoBridge.pause(); 1009 } 1010 } 1011 // Record the current scroll position before pausing. 1012 mLastIndex = mManageApplications.mListView.getFirstVisiblePosition(); 1013 View v = mManageApplications.mListView.getChildAt(0); 1014 mLastTop = 1015 (v == null) ? 0 : (v.getTop() - mManageApplications.mListView.getPaddingTop()); 1016 } 1017 1018 public void release() { 1019 mSession.release(); 1020 if (mExtraInfoBridge != null) { 1021 mExtraInfoBridge.release(); 1022 } 1023 } 1024 1025 public void rebuild(int sort) { 1026 if (sort == mLastSortMode) { 1027 return; 1028 } 1029 mLastSortMode = sort; 1030 rebuild(true); 1031 } 1032 1033 public void rebuild(boolean eraseold) { 1034 if (!mHasReceivedLoadEntries 1035 || (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) { 1036 // Don't rebuild the list until all the app entries are loaded. 1037 return; 1038 } 1039 ApplicationsState.AppFilter filterObj; 1040 Comparator<AppEntry> comparatorObj; 1041 boolean emulated = Environment.isExternalStorageEmulated(); 1042 if (emulated) { 1043 mWhichSize = SIZE_TOTAL; 1044 } else { 1045 mWhichSize = SIZE_INTERNAL; 1046 } 1047 filterObj = FILTERS[mFilterMode]; 1048 if (mCompositeFilter != null) { 1049 filterObj = new CompoundFilter(filterObj, mCompositeFilter); 1050 } 1051 if (!mManageApplications.mShowSystem) { 1052 if (LIST_TYPES_WITH_INSTANT.contains(mManageApplications.mListType)) { 1053 filterObj = new CompoundFilter(filterObj, 1054 ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT); 1055 } else { 1056 filterObj = new CompoundFilter(filterObj, 1057 ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER); 1058 } 1059 } 1060 switch (mLastSortMode) { 1061 case R.id.sort_order_size: 1062 switch (mWhichSize) { 1063 case SIZE_INTERNAL: 1064 comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR; 1065 break; 1066 case SIZE_EXTERNAL: 1067 comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR; 1068 break; 1069 default: 1070 comparatorObj = ApplicationsState.SIZE_COMPARATOR; 1071 break; 1072 } 1073 break; 1074 default: 1075 comparatorObj = ApplicationsState.ALPHA_COMPARATOR; 1076 break; 1077 } 1078 1079 filterObj = new CompoundFilter(filterObj, ApplicationsState.FILTER_NOT_HIDE); 1080 AppFilter finalFilterObj = filterObj; 1081 mBgHandler.post(() -> { 1082 final ArrayList<AppEntry> entries = mSession.rebuild(finalFilterObj, 1083 comparatorObj, false); 1084 if (entries != null) { 1085 mFgHandler.post(() -> onRebuildComplete(entries)); 1086 } 1087 }); 1088 } 1089 1090 1091 static private boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) { 1092 if (info1 == null || info2 == null) { 1093 return false; 1094 } 1095 if (info1.packageName == null || info2.packageName == null) { 1096 return false; 1097 } 1098 return info1.packageName.equals(info2.packageName); 1099 } 1100 1101 private ArrayList<ApplicationsState.AppEntry> removeDuplicateIgnoringUser( 1102 ArrayList<ApplicationsState.AppEntry> entries) { 1103 int size = entries.size(); 1104 // returnList will not have more entries than entries 1105 ArrayList<ApplicationsState.AppEntry> returnEntries = new 1106 ArrayList<ApplicationsState.AppEntry>(size); 1107 1108 // assume appinfo of same package but different users are grouped together 1109 PackageItemInfo lastInfo = null; 1110 for (int i = 0; i < size; i++) { 1111 AppEntry appEntry = entries.get(i); 1112 PackageItemInfo info = appEntry.info; 1113 if (!packageNameEquals(lastInfo, appEntry.info)) { 1114 returnEntries.add(appEntry); 1115 } 1116 lastInfo = info; 1117 } 1118 returnEntries.trimToSize(); 1119 return returnEntries; 1120 } 1121 1122 @Override 1123 public void onRebuildComplete(ArrayList<AppEntry> entries) { 1124 if (mFilterMode == FILTER_APPS_POWER_WHITELIST || 1125 mFilterMode == FILTER_APPS_POWER_WHITELIST_ALL) { 1126 entries = removeDuplicateIgnoringUser(entries); 1127 } 1128 mBaseEntries = entries; 1129 if (mBaseEntries != null) { 1130 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 1131 rebuildSections(); 1132 } else { 1133 mEntries = null; 1134 mSections = EMPTY_SECTIONS; 1135 mPositionToSectionIndex = null; 1136 } 1137 1138 notifyDataSetChanged(); 1139 // Restore the last scroll position if the number of entries added so far is bigger than 1140 // it. 1141 if (mLastIndex != -1 && getCount() > mLastIndex) { 1142 mManageApplications.mListView.setSelectionFromTop(mLastIndex, mLastTop); 1143 mLastIndex = -1; 1144 } 1145 1146 if (mSession.getAllApps().size() != 0 1147 && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) { 1148 mLoadingViewController.showContent(true /* animate */); 1149 } 1150 if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { 1151 // No enabled or disabled filters for usage access. 1152 return; 1153 } 1154 1155 mManageApplications.setHasDisabled(mState.haveDisabledApps()); 1156 mManageApplications.setHasInstant(mState.haveInstantApps()); 1157 } 1158 1159 private void rebuildSections() { 1160 if (mEntries != null && mManageApplications.mListView.isFastScrollEnabled()) { 1161 // Rebuild sections 1162 if (mIndex == null) { 1163 LocaleList locales = mContext.getResources().getConfiguration().getLocales(); 1164 if (locales.size() == 0) { 1165 locales = new LocaleList(Locale.ENGLISH); 1166 } 1167 AlphabeticIndex<Locale> index = new AlphabeticIndex<>(locales.get(0)); 1168 int localeCount = locales.size(); 1169 for (int i = 1; i < localeCount; i++) { 1170 index.addLabels(locales.get(i)); 1171 } 1172 // Ensure we always have some base English locale buckets 1173 index.addLabels(Locale.ENGLISH); 1174 mIndex = index.buildImmutableIndex(); 1175 } 1176 1177 ArrayList<SectionInfo> sections = new ArrayList<>(); 1178 int lastSecId = -1; 1179 int totalEntries = mEntries.size(); 1180 mPositionToSectionIndex = new int[totalEntries]; 1181 1182 for (int pos = 0; pos < totalEntries; pos++) { 1183 String label = mEntries.get(pos).label; 1184 int secId = mIndex.getBucketIndex(TextUtils.isEmpty(label) ? "" : label); 1185 if (secId != lastSecId) { 1186 lastSecId = secId; 1187 sections.add(new SectionInfo(mIndex.getBucket(secId).getLabel(), pos)); 1188 } 1189 mPositionToSectionIndex[pos] = sections.size() - 1; 1190 } 1191 mSections = sections.toArray(EMPTY_SECTIONS); 1192 } else { 1193 mSections = EMPTY_SECTIONS; 1194 mPositionToSectionIndex = null; 1195 } 1196 } 1197 1198 @VisibleForTesting 1199 void updateLoading() { 1200 final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0; 1201 if (appLoaded) { 1202 mLoadingViewController.showContent(false /* animate */); 1203 } else { 1204 mLoadingViewController.showLoadingViewDelayed(); 1205 } 1206 } 1207 1208 ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, 1209 ArrayList<ApplicationsState.AppEntry> origEntries) { 1210 if (prefix == null || prefix.length() == 0) { 1211 return origEntries; 1212 } else { 1213 String prefixStr = ApplicationsState.normalize(prefix.toString()); 1214 final String spacePrefixStr = " " + prefixStr; 1215 ArrayList<ApplicationsState.AppEntry> newEntries 1216 = new ArrayList<ApplicationsState.AppEntry>(); 1217 for (int i = 0; i < origEntries.size(); i++) { 1218 ApplicationsState.AppEntry entry = origEntries.get(i); 1219 String nlabel = entry.getNormalizedLabel(); 1220 if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) { 1221 newEntries.add(entry); 1222 } 1223 } 1224 return newEntries; 1225 } 1226 } 1227 1228 @Override 1229 public void onExtraInfoUpdated() { 1230 mHasReceivedBridgeCallback = true; 1231 rebuild(false); 1232 } 1233 1234 @Override 1235 public void onRunningStateChanged(boolean running) { 1236 mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running); 1237 } 1238 1239 @Override 1240 public void onPackageListChanged() { 1241 rebuild(false); 1242 } 1243 1244 @Override 1245 public void onPackageIconChanged() { 1246 // We ensure icons are loaded when their item is displayed, so 1247 // don't care about icons loaded in the background. 1248 } 1249 1250 @Override 1251 public void onLoadEntriesCompleted() { 1252 mHasReceivedLoadEntries = true; 1253 // We may have been skipping rebuilds until this came in, trigger one now. 1254 rebuild(false); 1255 } 1256 1257 @Override 1258 public void onPackageSizeChanged(String packageName) { 1259 for (int i = 0; i < mActive.size(); i++) { 1260 AppViewHolder holder = (AppViewHolder) mActive.get(i).getTag(); 1261 if (holder == null || holder.entry == null) { 1262 continue; 1263 } 1264 ApplicationInfo info = holder.entry.info; 1265 if (info == null) { 1266 continue; 1267 } 1268 if (holder.entry.info.packageName.equals(packageName)) { 1269 synchronized (holder.entry) { 1270 updateSummary(holder); 1271 } 1272 if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName) 1273 && mLastSortMode == R.id.sort_order_size) { 1274 // We got the size information for the last app the 1275 // user viewed, and are sorting by size... they may 1276 // have cleared data, so we immediately want to resort 1277 // the list with the new size to reflect it to the user. 1278 rebuild(false); 1279 } 1280 return; 1281 } 1282 } 1283 } 1284 1285 @Override 1286 public void onLauncherInfoChanged() { 1287 if (!mManageApplications.mShowSystem) { 1288 rebuild(false); 1289 } 1290 } 1291 1292 @Override 1293 public void onAllSizesComputed() { 1294 if (mLastSortMode == R.id.sort_order_size) { 1295 rebuild(false); 1296 } 1297 } 1298 1299 public void onExtraViewCompleted() { 1300 int size = mActive.size(); 1301 // If we have no elements, don't do anything. 1302 if (size < 1) { 1303 return; 1304 } 1305 AppViewHolder holder = (AppViewHolder) mActive.get(size - 1).getTag(); 1306 1307 // HACK: The extra view has no AppEntry -- and should be the only element without one. 1308 // Thus, if the last active element has no AppEntry, it is the extra view. 1309 if (holder == null || holder.entry != null) { 1310 return; 1311 } 1312 1313 mExtraViewController.setupView(holder); 1314 } 1315 1316 public int getCount() { 1317 if (mEntries == null) { 1318 return 0; 1319 } 1320 int extraViewAddition = 1321 (mExtraViewController != null && mExtraViewController.shouldShow()) ? 1 : 0; 1322 return mEntries.size() + extraViewAddition; 1323 } 1324 1325 public int getApplicationCount() { 1326 return mEntries != null ? mEntries.size() : 0; 1327 } 1328 1329 public Object getItem(int position) { 1330 if (position == mEntries.size()) { 1331 return mExtraViewController; 1332 } 1333 return mEntries.get(position); 1334 } 1335 1336 public ApplicationsState.AppEntry getAppEntry(int position) { 1337 return mEntries.get(position); 1338 } 1339 1340 public long getItemId(int position) { 1341 if (position == mEntries.size()) { 1342 return -1; 1343 } 1344 return mEntries.get(position).id; 1345 } 1346 1347 @Override 1348 public boolean areAllItemsEnabled() { 1349 return false; 1350 } 1351 1352 @Override 1353 public boolean isEnabled(int position) { 1354 if (position == mEntries.size() && mExtraViewController != null && 1355 mExtraViewController.shouldShow()) { 1356 return true; 1357 } 1358 1359 if (mManageApplications.mListType != LIST_TYPE_HIGH_POWER) { 1360 return true; 1361 } 1362 ApplicationsState.AppEntry entry = mEntries.get(position); 1363 return !PowerWhitelistBackend.getInstance().isSysWhitelisted(entry.info.packageName); 1364 } 1365 1366 public View getView(int position, View convertView, ViewGroup parent) { 1367 // A ViewHolder keeps references to children views to avoid unnecessary calls 1368 // to findViewById() on each row. 1369 AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater, 1370 convertView); 1371 convertView = holder.rootView; 1372 1373 // Handle the extra view if it is the last entry. 1374 if (mEntries != null && mExtraViewController != null && position == mEntries.size()) { 1375 mExtraViewController.setupView(holder); 1376 convertView.setEnabled(true); 1377 } else { 1378 // Bind the data efficiently with the holder 1379 ApplicationsState.AppEntry entry = mEntries.get(position); 1380 synchronized (entry) { 1381 holder.entry = entry; 1382 if (entry.label != null) { 1383 holder.appName.setText(entry.label); 1384 } 1385 mState.ensureIcon(entry); 1386 if (entry.icon != null) { 1387 holder.appIcon.setImageDrawable(entry.icon); 1388 } 1389 updateSummary(holder); 1390 updateDisableView(holder.disabled, entry.info); 1391 } 1392 convertView.setEnabled(isEnabled(position)); 1393 } 1394 1395 mActive.remove(convertView); 1396 mActive.add(convertView); 1397 return convertView; 1398 } 1399 1400 @VisibleForTesting 1401 void updateDisableView(TextView view, ApplicationInfo info) { 1402 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 1403 view.setVisibility(View.VISIBLE); 1404 view.setText(R.string.not_installed); 1405 } else if (!info.enabled || info.enabledSetting 1406 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 1407 view.setVisibility(View.VISIBLE); 1408 view.setText(R.string.disabled); 1409 } else { 1410 view.setVisibility(View.GONE); 1411 } 1412 } 1413 1414 private void updateSummary(AppViewHolder holder) { 1415 switch (mManageApplications.mListType) { 1416 case LIST_TYPE_NOTIFICATION: 1417 if (holder.entry.extraInfo != null) { 1418 holder.summary.setText(InstalledAppDetails.getNotificationSummary( 1419 (AppRow) holder.entry.extraInfo, mContext)); 1420 } else { 1421 holder.summary.setText(null); 1422 } 1423 break; 1424 1425 case LIST_TYPE_USAGE_ACCESS: 1426 if (holder.entry.extraInfo != null) { 1427 holder.summary.setText((new UsageState((PermissionState) holder.entry 1428 .extraInfo)).isPermissible() 1429 ? R.string.app_permission_summary_allowed 1430 : R.string.app_permission_summary_not_allowed); 1431 } else { 1432 holder.summary.setText(null); 1433 } 1434 break; 1435 1436 case LIST_TYPE_HIGH_POWER: 1437 holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry)); 1438 break; 1439 1440 case LIST_TYPE_OVERLAY: 1441 holder.summary.setText(DrawOverlayDetails.getSummary(mContext, holder.entry)); 1442 break; 1443 1444 case LIST_TYPE_WRITE_SETTINGS: 1445 holder.summary.setText(WriteSettingsDetails.getSummary(mContext, 1446 holder.entry)); 1447 break; 1448 1449 case LIST_TYPE_MANAGE_SOURCES: 1450 holder.summary.setText(ExternalSourcesDetails.getPreferenceSummary(mContext, 1451 holder.entry)); 1452 break; 1453 1454 default: 1455 holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize); 1456 break; 1457 } 1458 } 1459 1460 @Override 1461 public Filter getFilter() { 1462 return mFilter; 1463 } 1464 1465 @Override 1466 public void onMovedToScrapHeap(View view) { 1467 mActive.remove(view); 1468 } 1469 1470 @Override 1471 public Object[] getSections() { 1472 return mSections; 1473 } 1474 1475 @Override 1476 public int getPositionForSection(int sectionIndex) { 1477 return mSections[sectionIndex].position; 1478 } 1479 1480 @Override 1481 public int getSectionForPosition(int position) { 1482 return mPositionToSectionIndex[position]; 1483 } 1484 } 1485 1486 private static class SummaryProvider implements SummaryLoader.SummaryProvider { 1487 1488 private final Context mContext; 1489 private final SummaryLoader mLoader; 1490 private ApplicationsState.Session mSession; 1491 1492 private SummaryProvider(Context context, SummaryLoader loader) { 1493 mContext = context; 1494 mLoader = loader; 1495 } 1496 1497 @Override 1498 public void setListening(boolean listening) { 1499 if (listening) { 1500 new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON, 1501 new PackageManagerWrapperImpl(mContext.getPackageManager())) { 1502 @Override 1503 protected void onCountComplete(int num) { 1504 mLoader.setSummary(SummaryProvider.this, 1505 mContext.getString(R.string.apps_summary, num)); 1506 } 1507 }.execute(); 1508 } 1509 } 1510 } 1511 1512 private static class SectionInfo { 1513 final String label; 1514 final int position; 1515 1516 public SectionInfo(String label, int position) { 1517 this.label = label; 1518 this.position = position; 1519 } 1520 1521 @Override 1522 public String toString() { 1523 return label; 1524 } 1525 } 1526 1527 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 1528 = new SummaryLoader.SummaryProviderFactory() { 1529 @Override 1530 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 1531 SummaryLoader summaryLoader) { 1532 return new SummaryProvider(activity, summaryLoader); 1533 } 1534 }; 1535 } 1536