Skip to content

Commit

Permalink
appops: Load the app list asynchronously
Browse files Browse the repository at this point in the history
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
  • Loading branch information
defer authored and PRJosh committed Jan 28, 2016
1 parent ddf52d7 commit e60aa6c
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 60 deletions.
38 changes: 33 additions & 5 deletions res/layout/privacy_guard_manager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,38 @@
android:textColor="@android:color/white"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="gone" />
<ListView android:id="@+id/apps_list"
android:drawSelectorOnTop="false"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle" />
android:layout_height="0dp"
android:layout_weight="1">

<ListView android:id="@+id/apps_list"
android:drawSelectorOnTop="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle"
android:visibility="gone"/>

<LinearLayout android:id="@+id/loading_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:gravity="center">

<ProgressBar style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/settings_safetylegal_activity_loading"
android:paddingTop="4dip"
android:singleLine="true" />

</LinearLayout>
</FrameLayout>

</LinearLayout>
110 changes: 110 additions & 0 deletions src/com/android/settings/privacyguard/AppInfoLoader.java
Original file line number Diff line number Diff line change
@@ -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<List<AppInfo>> {
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<AppInfo> 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<AppInfo> loadInstalledApps() {
List<AppInfo> apps = new ArrayList<AppInfo>();
List<PackageInfo> 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<AppInfo>() {
@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;
}

}
101 changes: 46 additions & 55 deletions src/com/android/settings/privacyguard/PrivacyGuardManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@

package com.android.settings.privacyguard;

import android.view.animation.AnimationUtils;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.Dialog;
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;
Expand All @@ -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<List<PrivacyGuardManager.AppInfo>> {

private static final String TAG = "PrivacyGuardManager";

private TextView mNoUserAppsInstalled;
private ListView mAppsList;
private View mLoadingContainer;
private PrivacyGuardAppListAdapter mAdapter;
private List<AppInfo> mApps;

private PackageManager mPm;
private Activity mActivity;

private SharedPreferences mPreferences;
Expand All @@ -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);
Expand All @@ -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)) {
Expand All @@ -139,7 +141,7 @@ public void onActivityCreated(Bundle savedInstanceState) {
}

// load apps and construct the list
loadApps();
scheduleAppsLoad();

setHasOptionsMenu(true);
}
Expand Down Expand Up @@ -174,17 +176,49 @@ 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);
mSavedFirstVisiblePosition = AdapterView.INVALID_POSITION;
}
}

private void loadApps() {
mApps = loadInstalledApps();
@Override
public Loader<List<AppInfo>> 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<List<AppInfo>> loader, List<AppInfo> 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<List<AppInfo>> 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()) {
Expand Down Expand Up @@ -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<AppInfo> loadInstalledApps() {
List<AppInfo> apps = new ArrayList<AppInfo>();
List<PackageInfo> 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<AppInfo>() {
@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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit e60aa6c

Please sign in to comment.