From e60aa6c66f682c766fc3ef8230c20b7b510bc0ad Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 14 Jan 2015 16:06:23 +0000 Subject: [PATCH] appops: Load the app list asynchronously The AppList is pretty big and it is loading in the UI thread. This is noticeable even on high-end chips and there is a definite possibility that it throws an ANR on lower-end ones. This patchset adds an asynchronous loader and updates the privacy guard to use it. Change-Id: I81f3fb64604af07a351f8cbdfffa7454389e2cee --- res/layout/privacy_guard_manager.xml | 38 +++++- .../settings/privacyguard/AppInfoLoader.java | 110 ++++++++++++++++++ .../privacyguard/PrivacyGuardManager.java | 101 ++++++++-------- 3 files changed, 189 insertions(+), 60 deletions(-) create mode 100644 src/com/android/settings/privacyguard/AppInfoLoader.java diff --git a/res/layout/privacy_guard_manager.xml b/res/layout/privacy_guard_manager.xml index 98d9ae4e764..15328f6eaa5 100644 --- a/res/layout/privacy_guard_manager.xml +++ b/res/layout/privacy_guard_manager.xml @@ -37,10 +37,38 @@ android:textColor="@android:color/white" android:textAppearance="?android:attr/textAppearanceMedium" android:visibility="gone" /> - + android:layout_height="0dp" + android:layout_weight="1"> + + + + + + + + + + + + diff --git a/src/com/android/settings/privacyguard/AppInfoLoader.java b/src/com/android/settings/privacyguard/AppInfoLoader.java new file mode 100644 index 00000000000..8e3ea5fb083 --- /dev/null +++ b/src/com/android/settings/privacyguard/AppInfoLoader.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.privacyguard; + +import android.app.AppOpsManager; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageInfo; + +import com.android.settings.privacyguard.PrivacyGuardManager.AppInfo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An asynchronous loader implementation that loads AppInfo structures. + */ +/* package */ class AppInfoLoader extends AsyncTaskLoader> { + private PackageManager mPm; + private boolean mShowSystemApps; + private AppOpsManager mAppOps; + + public AppInfoLoader(Context context, boolean showSystemApps) { + super(context); + mPm = context.getPackageManager(); + mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mShowSystemApps = showSystemApps; + } + + @Override + public List loadInBackground() { + return loadInstalledApps(); + } + + @Override + public void onStartLoading() { + forceLoad(); + } + + @Override + public void onStopLoading() { + cancelLoad(); + } + + @Override + protected void onReset() { + cancelLoad(); + } + + /** + * Uses the package manager to query for all currently installed apps + * for the list. + * + * @return the complete List off installed applications (@code PrivacyGuardAppInfo) + */ + private List loadInstalledApps() { + List apps = new ArrayList(); + List packages = mPm.getInstalledPackages( + PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); + + for (PackageInfo info : packages) { + final ApplicationInfo appInfo = info.applicationInfo; + + // skip all system apps if they shall not be included + if (!mShowSystemApps && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + continue; + } + + AppInfo app = new AppInfo(); + app.title = appInfo.loadLabel(mPm).toString(); + app.packageName = info.packageName; + app.enabled = appInfo.enabled; + app.uid = info.applicationInfo.uid; + app.privacyGuardEnabled = mAppOps.getPrivacyGuardSettingForPackage( + app.uid, app.packageName); + apps.add(app); + } + + // sort the apps by their enabled state, then by title + Collections.sort(apps, new Comparator() { + @Override + public int compare(AppInfo lhs, AppInfo rhs) { + if (lhs.enabled != rhs.enabled) { + return lhs.enabled ? -1 : 1; + } + return lhs.title.compareToIgnoreCase(rhs.title); + } + }); + + return apps; + } + +} diff --git a/src/com/android/settings/privacyguard/PrivacyGuardManager.java b/src/com/android/settings/privacyguard/PrivacyGuardManager.java index 74eeee0d8d7..b187782c0ef 100644 --- a/src/com/android/settings/privacyguard/PrivacyGuardManager.java +++ b/src/com/android/settings/privacyguard/PrivacyGuardManager.java @@ -16,6 +16,7 @@ package com.android.settings.privacyguard; +import android.view.animation.AnimationUtils; import android.app.Activity; import android.app.AlertDialog; import android.app.AppOpsManager; @@ -23,13 +24,12 @@ import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; +import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; +import android.content.Loader; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; @@ -54,23 +54,24 @@ import com.android.settings.applications.AppOpsDetails; import com.android.settings.applications.AppOpsState; import com.android.settings.applications.AppOpsState.OpsTemplate; +import com.android.settings.privacyguard.AppInfoLoader; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; public class PrivacyGuardManager extends Fragment - implements OnItemClickListener, OnItemLongClickListener { + implements OnItemClickListener, OnItemLongClickListener, + LoaderManager.LoaderCallbacks> { private static final String TAG = "PrivacyGuardManager"; private TextView mNoUserAppsInstalled; private ListView mAppsList; + private View mLoadingContainer; private PrivacyGuardAppListAdapter mAdapter; private List mApps; - private PackageManager mPm; private Activity mActivity; private SharedPreferences mPreferences; @@ -97,7 +98,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mActivity = getActivity(); - mPm = mActivity.getPackageManager(); mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE); return inflater.inflate(R.layout.privacy_guard_manager, container, false); @@ -123,6 +123,8 @@ public void onActivityCreated(Bundle savedInstanceState) { mAppsList.setOnItemClickListener(this); mAppsList.setOnItemLongClickListener(this); + mLoadingContainer = mActivity.findViewById(R.id.loading_container); + // get shared preference mPreferences = mActivity.getSharedPreferences("privacy_guard_manager", Activity.MODE_PRIVATE); if (!mPreferences.getBoolean("first_help_shown", false)) { @@ -139,7 +141,7 @@ public void onActivityCreated(Bundle savedInstanceState) { } // load apps and construct the list - loadApps(); + scheduleAppsLoad(); setHasOptionsMenu(true); } @@ -174,7 +176,7 @@ public void onResume() { super.onResume(); // rebuild the list; the user might have changed settings inbetween - loadApps(); + scheduleAppsLoad(); if (mSavedFirstVisiblePosition != AdapterView.INVALID_POSITION) { mAppsList.setSelectionFromTop(mSavedFirstVisiblePosition, mSavedFirstItemOffset); @@ -182,9 +184,41 @@ public void onResume() { } } - private void loadApps() { - mApps = loadInstalledApps(); + @Override + public Loader> onCreateLoader(int id, Bundle args) { + mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + mAppsList.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + + mAppsList.setVisibility(View.INVISIBLE); + mLoadingContainer.setVisibility(View.VISIBLE); + return new AppInfoLoader(mActivity, shouldShowSystemApps()); + } + + @Override + public void onLoadFinished(Loader> loader, List apps) { + mApps = apps; + prepareAppAdapter(); + + mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + mAppsList.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + + mLoadingContainer.setVisibility(View.INVISIBLE); + mAppsList.setVisibility(View.VISIBLE); + } + + @Override + public void onLoaderReset(Loader> loader) { + } + + private void scheduleAppsLoad() { + getLoaderManager().restartLoader(0, null, this); + } + private void prepareAppAdapter() { // if app list is empty inform the user // else go ahead and construct the list if (mApps == null || mApps.isEmpty()) { @@ -265,49 +299,6 @@ public boolean onItemLongClick(AdapterView parent, View view, int position, l return true; } - /** - * Uses the package manager to query for all currently installed apps - * for the list. - * - * @return the complete List off installed applications (@code PrivacyGuardAppInfo) - */ - private List loadInstalledApps() { - List apps = new ArrayList(); - List packages = mPm.getInstalledPackages( - PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); - boolean showSystemApps = shouldShowSystemApps(); - - for (PackageInfo info : packages) { - final ApplicationInfo appInfo = info.applicationInfo; - - // skip all system apps if they shall not be included - if (!showSystemApps && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - continue; - } - - AppInfo app = new AppInfo(); - app.title = appInfo.loadLabel(mPm).toString(); - app.packageName = info.packageName; - app.enabled = appInfo.enabled; - app.uid = info.applicationInfo.uid; - app.privacyGuardEnabled = mAppOps.getPrivacyGuardSettingForPackage( - app.uid, app.packageName); - apps.add(app); - } - - // sort the apps by their enabled state, then by title - Collections.sort(apps, new Comparator() { - @Override - public int compare(AppInfo lhs, AppInfo rhs) { - if (lhs.enabled != rhs.enabled) { - return lhs.enabled ? -1 : 1; - } - return lhs.title.compareToIgnoreCase(rhs.title); - } - }); - - return apps; - } private boolean shouldShowSystemApps() { return mPreferences.getBoolean("show_system_apps", false); @@ -393,7 +384,7 @@ public boolean onOptionsItemSelected(MenuItem item) { // shared preference and rebuild the list item.setChecked(!item.isChecked()); mPreferences.edit().putBoolean(prefName, item.isChecked()).commit(); - loadApps(); + scheduleAppsLoad(); return true; case R.id.advanced: Intent i = new Intent(Intent.ACTION_MAIN);