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