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