diff --git a/res/values/arrays.xml b/res/values/arrays.xml index e5990c5c63b..ad2b3088b5f 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -557,14 +557,14 @@ - Coarse Location - Fine Location + Coarse location + Fine location GPS Vibrate - Contacts: Read - Contacts: Write - Call Log: Read - Call Log: Write + Read contacts + Write contacts + Read calls + Write calls diff --git a/res/values/strings.xml b/res/values/strings.xml index d9b28ea8a70..419cb11c724 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2565,6 +2565,8 @@ All + + Disabled Downloaded diff --git a/src/com/android/settings/applications/AppOpsCategory.java b/src/com/android/settings/applications/AppOpsCategory.java index 940ba0b30e7..1eafbbfe3c9 100644 --- a/src/com/android/settings/applications/AppOpsCategory.java +++ b/src/com/android/settings/applications/AppOpsCategory.java @@ -1,9 +1,23 @@ +/** + * Copyright (C) 2013 The Android Open Source 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.applications; -import android.app.AppOpsManager; import android.app.ListFragment; import android.app.LoaderManager; -import android.app.AppOpsManager.OpEntry; import android.content.AsyncTaskLoader; import android.content.BroadcastReceiver; import android.content.Context; @@ -11,14 +25,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.Loader; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -28,154 +37,26 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import java.io.File; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import com.android.settings.R; +import com.android.settings.applications.AppOpsState.AppOpEntry; public class AppOpsCategory extends ListFragment implements - LoaderManager.LoaderCallbacks> { + LoaderManager.LoaderCallbacks> { // This is the Adapter being used to display the list's data. AppListAdapter mAdapter; - /** - * This class holds the per-item data in our Loader. - */ - public static class AppEntry { - private final AppListLoader mLoader; - private final ApplicationInfo mInfo; - private final File mApkFile; - private String mLabel; - private Drawable mIcon; - private boolean mMounted; - - public AppEntry(AppListLoader loader, ApplicationInfo info) { - mLoader = loader; - mInfo = info; - mApkFile = new File(info.sourceDir); - } - - public ApplicationInfo getApplicationInfo() { - return mInfo; - } - - public String getLabel() { - return mLabel; - } - - public Drawable getIcon() { - if (mIcon == null) { - if (mApkFile.exists()) { - mIcon = mInfo.loadIcon(mLoader.mPm); - return mIcon; - } else { - mMounted = false; - } - } else if (!mMounted) { - // If the app wasn't mounted but is now mounted, reload - // its icon. - if (mApkFile.exists()) { - mMounted = true; - mIcon = mInfo.loadIcon(mLoader.mPm); - return mIcon; - } - } else { - return mIcon; - } - - return mLoader.getContext().getResources().getDrawable( - android.R.drawable.sym_def_app_icon); - } - - @Override public String toString() { - return mLabel; - } - - void loadLabel(Context context) { - if (mLabel == null || !mMounted) { - if (!mApkFile.exists()) { - mMounted = false; - mLabel = mInfo.packageName; - } else { - mMounted = true; - CharSequence label = mInfo.loadLabel(context.getPackageManager()); - mLabel = label != null ? label.toString() : mInfo.packageName; - } - } - } - } - public AppOpsCategory() { } - public AppOpsCategory(int[] ops, String[] perms) { + public AppOpsCategory(AppOpsState.OpsTemplate template) { Bundle args = new Bundle(); - args.putIntArray("ops", ops); - args.putStringArray("perms", perms); + args.putParcelable("template", template); setArguments(args); } - /** - * This class holds the per-item data in our Loader. - */ - public static class AppOpEntry { - private final AppOpsManager.PackageOps mPkgOps; - private final AppOpsManager.OpEntry mOp; - private final AppEntry mApp; - - public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app) { - mPkgOps = pkg; - mOp = op; - mApp = app; - } - - public AppEntry getAppEntry() { - return mApp; - } - - public AppOpsManager.PackageOps getPackageOps() { - return mPkgOps; - } - - public AppOpsManager.OpEntry getOpEntry() { - return mOp; - } - - public long getTime() { - return mOp.getTime(); - } - - @Override public String toString() { - return mApp.getLabel(); - } - } - - /** - * Perform alphabetical comparison of application entry objects. - */ - public static final Comparator APP_OP_COMPARATOR = new Comparator() { - private final Collator sCollator = Collator.getInstance(); - @Override - public int compare(AppOpEntry object1, AppOpEntry object2) { - if (object1.getOpEntry().isRunning() != object2.getOpEntry().isRunning()) { - // Currently running ops go first. - return object1.getOpEntry().isRunning() ? -1 : 1; - } - if (object1.getTime() != object2.getTime()) { - // More recent times go first. - return object1.getTime() > object2.getTime() ? -1 : 1; - } - return sCollator.compare(object1.getAppEntry().getLabel(), - object2.getAppEntry().getLabel()); - } - }; - /** * Helper for determining if the configuration has changed in an interesting * way so we need to rebuild the app list. @@ -228,76 +109,20 @@ public class AppOpsCategory extends ListFragment implements */ public static class AppListLoader extends AsyncTaskLoader> { final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); - final AppOpsManager mAppOps; - final PackageManager mPm; - final int[] mOps; - final String[] mPerms; - - final HashMap mAppEntries = new HashMap(); + final AppOpsState mState; + final AppOpsState.OpsTemplate mTemplate; List mApps; PackageIntentReceiver mPackageObserver; - public AppListLoader(Context context, int[] ops, String[] perms) { + public AppListLoader(Context context, AppOpsState.OpsTemplate template) { super(context); - mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); - mPm = context.getPackageManager(); - mOps = ops; - mPerms = perms; + mState = new AppOpsState(context); + mTemplate = template; } @Override public List loadInBackground() { - final Context context = getContext(); - - List pkgs = mAppOps.getPackagesForOps(mOps); - List entries = new ArrayList(pkgs.size()); - for (int i=0; i apps = mPm.getPackagesHoldingPermissions(mPerms, 0); - for (int i=0; i dummyOps = new ArrayList(); - AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry(0, 0, 0); - dummyOps.add(opEntry); - AppOpsManager.PackageOps pkgOps = new AppOpsManager.PackageOps( - appInfo.packageName, appInfo.applicationInfo.uid, dummyOps); - AppOpEntry entry = new AppOpEntry(pkgOps, opEntry, appEntry); - entries.add(entry); - } - } - } - - // Sort the list. - Collections.sort(entries, APP_OP_COMPARATOR); - - // Done! - return entries; + return mState.buildState(mTemplate); } /** @@ -409,15 +234,15 @@ public class AppOpsCategory extends ListFragment implements } public static class AppListAdapter extends ArrayAdapter { + private final Resources mResources; private final LayoutInflater mInflater; private final CharSequence[] mOpNames; - private final CharSequence mRunningStr; public AppListAdapter(Context context) { super(context, android.R.layout.simple_list_item_2); + mResources = context.getResources(); mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mOpNames = context.getResources().getTextArray(R.array.app_ops_names); - mRunningStr = context.getResources().getText(R.string.app_ops_running); + mOpNames = mResources.getTextArray(R.array.app_ops_names); } public void setData(List data) { @@ -427,16 +252,6 @@ public class AppOpsCategory extends ListFragment implements } } - CharSequence opTimeToString(AppOpsManager.OpEntry op) { - if (op.isRunning()) { - return mRunningStr; - } - return DateUtils.getRelativeTimeSpanString(op.getTime(), - System.currentTimeMillis(), - DateUtils.MINUTE_IN_MILLIS, - DateUtils.FORMAT_ABBREV_RELATIVE); - } - /** * Populate new items in the list. */ @@ -453,14 +268,8 @@ public class AppOpsCategory extends ListFragment implements ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable( item.getAppEntry().getIcon()); ((TextView)view.findViewById(R.id.app_name)).setText(item.getAppEntry().getLabel()); - if (item.getOpEntry().getTime() != 0) { - ((TextView)view.findViewById(R.id.op_name)).setText( - mOpNames[item.getOpEntry().getOp()]); - ((TextView)view.findViewById(R.id.op_time)).setText(opTimeToString(item.getOpEntry())); - } else { - ((TextView)view.findViewById(R.id.op_name)).setText(""); - ((TextView)view.findViewById(R.id.op_time)).setText(""); - } + ((TextView)view.findViewById(R.id.op_name)).setText(item.getLabelText(mOpNames)); + ((TextView)view.findViewById(R.id.op_time)).setText(item.getTimeText(mResources)); return view; } @@ -495,13 +304,11 @@ public class AppOpsCategory extends ListFragment implements @Override public Loader> onCreateLoader(int id, Bundle args) { Bundle fargs = getArguments(); - int[] ops = null; - String[] perms = null; + AppOpsState.OpsTemplate template = null; if (fargs != null) { - ops = fargs.getIntArray("ops"); - perms = fargs.getStringArray("perms"); + template = (AppOpsState.OpsTemplate)fargs.getParcelable("template"); } - return new AppListLoader(getActivity(), ops, perms); + return new AppListLoader(getActivity(), template); } @Override public void onLoadFinished(Loader> loader, List data) { diff --git a/src/com/android/settings/applications/AppOpsDetails.java b/src/com/android/settings/applications/AppOpsDetails.java new file mode 100644 index 00000000000..2a071f20d91 --- /dev/null +++ b/src/com/android/settings/applications/AppOpsDetails.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2013 The Android Open Source 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.applications; + +import android.app.Fragment; + +public class AppOpsDetails extends Fragment { + +} diff --git a/src/com/android/settings/applications/AppOpsState.java b/src/com/android/settings/applications/AppOpsState.java new file mode 100644 index 00000000000..2bd724b12e9 --- /dev/null +++ b/src/com/android/settings/applications/AppOpsState.java @@ -0,0 +1,404 @@ +/** + * Copyright (C) 2013 The Android Open Source 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.applications; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.DateUtils; + +import com.android.settings.R; + +import java.io.File; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +public class AppOpsState { + final Context mContext; + final AppOpsManager mAppOps; + final PackageManager mPm; + + List mApps; + + public AppOpsState(Context context) { + mContext = context; + mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + mPm = context.getPackageManager(); + } + + public static class OpsTemplate implements Parcelable { + public final int[] ops; + public final String[] perms; + public final int[] permOps; + + public OpsTemplate(int[] _ops, String[] _perms, int[] _permOps) { + ops = _ops; + perms = _perms; + permOps = _permOps; + } + + OpsTemplate(Parcel src) { + ops = src.createIntArray(); + perms = src.createStringArray(); + permOps = src.createIntArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeIntArray(ops); + dest.writeStringArray(perms); + dest.writeIntArray(permOps); + } + + public static final Creator CREATOR = new Creator() { + @Override public OpsTemplate createFromParcel(Parcel source) { + return new OpsTemplate(source); + } + + @Override public OpsTemplate[] newArray(int size) { + return new OpsTemplate[size]; + } + }; + } + + public static final OpsTemplate LOCATION_TEMPLATE = new OpsTemplate( + new int[] { AppOpsManager.OP_COARSE_LOCATION, + AppOpsManager.OP_FINE_LOCATION, + AppOpsManager.OP_GPS }, + new String[] { android.Manifest.permission.ACCESS_COARSE_LOCATION, + android.Manifest.permission.ACCESS_FINE_LOCATION }, + new int[] { AppOpsManager.OP_COARSE_LOCATION, + AppOpsManager.OP_FINE_LOCATION } + ); + + public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate( + new int[] { AppOpsManager.OP_READ_CONTACTS, + AppOpsManager.OP_WRITE_CONTACTS, + AppOpsManager.OP_READ_CALL_LOG, + AppOpsManager.OP_WRITE_CALL_LOG }, + new String[] { android.Manifest.permission.READ_CONTACTS, + android.Manifest.permission.WRITE_CONTACTS, + android.Manifest.permission.READ_CALL_LOG, + android.Manifest.permission.WRITE_CALL_LOG }, + new int[] { AppOpsManager.OP_READ_CONTACTS, + AppOpsManager.OP_WRITE_CONTACTS, + AppOpsManager.OP_READ_CALL_LOG, + AppOpsManager.OP_WRITE_CALL_LOG } + ); + + public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate( + new int[] { AppOpsManager.OP_VIBRATE }, + new String[] { android.Manifest.permission.VIBRATE }, + new int[] { AppOpsManager.OP_VIBRATE } + ); + + /** + * This class holds the per-item data in our Loader. + */ + public static class AppEntry { + private final AppOpsState mState; + private final ApplicationInfo mInfo; + private final File mApkFile; + private String mLabel; + private Drawable mIcon; + private boolean mMounted; + + public AppEntry(AppOpsState state, ApplicationInfo info) { + mState = state; + mInfo = info; + mApkFile = new File(info.sourceDir); + } + + public ApplicationInfo getApplicationInfo() { + return mInfo; + } + + public String getLabel() { + return mLabel; + } + + public Drawable getIcon() { + if (mIcon == null) { + if (mApkFile.exists()) { + mIcon = mInfo.loadIcon(mState.mPm); + return mIcon; + } else { + mMounted = false; + } + } else if (!mMounted) { + // If the app wasn't mounted but is now mounted, reload + // its icon. + if (mApkFile.exists()) { + mMounted = true; + mIcon = mInfo.loadIcon(mState.mPm); + return mIcon; + } + } else { + return mIcon; + } + + return mState.mContext.getResources().getDrawable( + android.R.drawable.sym_def_app_icon); + } + + @Override public String toString() { + return mLabel; + } + + void loadLabel(Context context) { + if (mLabel == null || !mMounted) { + if (!mApkFile.exists()) { + mMounted = false; + mLabel = mInfo.packageName; + } else { + mMounted = true; + CharSequence label = mInfo.loadLabel(context.getPackageManager()); + mLabel = label != null ? label.toString() : mInfo.packageName; + } + } + } + } + + /** + * This class holds the per-item data in our Loader. + */ + public static class AppOpEntry { + private final AppOpsManager.PackageOps mPkgOps; + private final ArrayList mOps + = new ArrayList(); + private final AppEntry mApp; + + public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app) { + mPkgOps = pkg; + mOps.add(op); + mApp = app; + } + + public void addOp(AppOpsManager.OpEntry op) { + for (int i=0; i op.getTime()) { + mOps.add(i, op); + return; + } + } + mOps.add(op); + } + + public AppEntry getAppEntry() { + return mApp; + } + + public AppOpsManager.PackageOps getPackageOps() { + return mPkgOps; + } + + public int getNumOpEntry() { + return mOps.size(); + } + + public AppOpsManager.OpEntry getOpEntry(int pos) { + return mOps.get(pos); + } + + public CharSequence getLabelText(CharSequence opNames[]) { + if (getNumOpEntry() == 1) { + return opNames[getOpEntry(0).getOp()]; + } else { + StringBuilder builder = new StringBuilder(); + for (int i=0; i 0) { + builder.append(", "); + } + builder.append(opNames[getOpEntry(i).getOp()]); + } + return builder.toString(); + } + } + + public CharSequence getTimeText(Resources res) { + if (isRunning()) { + return res.getText(R.string.app_ops_running); + } + if (getTime() > 0) { + return DateUtils.getRelativeTimeSpanString(getTime(), + System.currentTimeMillis(), + DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE); + } + return ""; + } + + public boolean isRunning() { + return mOps.get(0).isRunning(); + } + + public long getTime() { + return mOps.get(0).getTime(); + } + + @Override public String toString() { + return mApp.getLabel(); + } + } + + /** + * Perform alphabetical comparison of application entry objects. + */ + public static final Comparator APP_OP_COMPARATOR = new Comparator() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppOpEntry object1, AppOpEntry object2) { + if (object1.isRunning() != object2.isRunning()) { + // Currently running ops go first. + return object1.isRunning() ? -1 : 1; + } + if (object1.getTime() != object2.getTime()) { + // More recent times go first. + return object1.getTime() > object2.getTime() ? -1 : 1; + } + return sCollator.compare(object1.getAppEntry().getLabel(), + object2.getAppEntry().getLabel()); + } + }; + + private void addOp(List entries, AppOpsManager.PackageOps pkgOps, + AppEntry appEntry, AppOpsManager.OpEntry opEntry) { + if (entries.size() > 0) { + AppOpEntry last = entries.get(entries.size()-1); + if (last.getAppEntry() == appEntry) { + boolean lastExe = last.getTime() != 0; + boolean entryExe = opEntry.getTime() != 0; + if (lastExe == entryExe) { + last.addOp(opEntry); + return; + } + } + } + AppOpEntry entry = new AppOpEntry(pkgOps, opEntry, appEntry); + entries.add(entry); + } + + public List buildState(OpsTemplate tpl) { + return buildState(tpl, 0, null); + } + + public List buildState(OpsTemplate tpl, int uid, String packageName) { + final Context context = mContext; + + final HashMap appEntries = new HashMap(); + + List pkgs; + if (packageName != null) { + pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops); + } else { + pkgs = mAppOps.getPackagesForOps(tpl.ops); + } + List entries = new ArrayList(pkgs.size()); + for (int i=0; i apps; + if (packageName != null) { + apps = new ArrayList(); + try { + PackageInfo pi = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); + apps.add(pi); + } catch (NameNotFoundException e) { + } + } else { + apps = mPm.getPackagesHoldingPermissions(tpl.perms, 0); + } + for (int i=0; i dummyOps + = new ArrayList(); + AppOpsManager.PackageOps pkgOps = new AppOpsManager.PackageOps( + appInfo.packageName, appInfo.applicationInfo.uid, dummyOps); + for (int j=0; j mTabs = new ArrayList(); + private int mNumTabs; TabInfo mCurTab = null; // Size resource used for packages whose size computation failed for some reason @@ -447,6 +450,7 @@ public class ManageApplications extends Fragment implements static final int LIST_TYPE_RUNNING = 1; static final int LIST_TYPE_SDCARD = 2; static final int LIST_TYPE_ALL = 3; + static final int LIST_TYPE_DISABLED = 4; private boolean mShowBackground = false; @@ -464,7 +468,7 @@ public class ManageApplications extends Fragment implements @Override public int getCount() { - return mTabs.size(); + return mNumTabs; } @Override @@ -472,6 +476,7 @@ public class ManageApplications extends Fragment implements TabInfo tab = mTabs.get(position); View root = tab.build(mInflater, mContentContainer, mRootView); container.addView(root); + root.setTag(R.id.name, tab); return root; } @@ -485,6 +490,12 @@ public class ManageApplications extends Fragment implements return view == object; } + @Override + public int getItemPosition(Object object) { + return super.getItemPosition(object); + //return ((TabInfo)((View)object).getTag(R.id.name)).mListType; + } + @Override public CharSequence getPageTitle(int position) { return mTabs.get(position).mLabel; @@ -607,8 +618,11 @@ public class ManageApplications extends Fragment implements mWhichSize = SIZE_EXTERNAL; } break; + case FILTER_APPS_DISABLED: + filterObj = ApplicationsState.DISABLED_FILTER; + break; default: - filterObj = null; + filterObj = ApplicationsState.ALL_ENABLED_FILTER; break; } switch (mLastSortMode) { @@ -869,6 +883,13 @@ public class ManageApplications extends Fragment implements getActivity().getString(R.string.filter_apps_all), LIST_TYPE_ALL, this, savedInstanceState); mTabs.add(tab); + + tab = new TabInfo(this, mApplicationsState, + getActivity().getString(R.string.filter_apps_disabled), + LIST_TYPE_DISABLED, this, savedInstanceState); + mTabs.add(tab); + + mNumTabs = mTabs.size(); } @@ -922,6 +943,7 @@ public class ManageApplications extends Fragment implements public void onResume() { super.onResume(); mActivityResumed = true; + updateNumTabs(); updateCurrentTab(mViewPager.getCurrentItem()); updateOptionsMenu(); } @@ -975,6 +997,16 @@ public class ManageApplications extends Fragment implements } } + private void updateNumTabs() { + int newNum = mApplicationsState.haveDisabledApps() ? mTabs.size() : (mTabs.size()-1); + if (newNum != mNumTabs) { + mNumTabs = newNum; + if (mViewPager != null) { + mViewPager.getAdapter().notifyDataSetChanged(); + } + } + } + TabInfo tabForType(int type) { for (int i = 0; i < mTabs.size(); i++) { TabInfo tab = mTabs.get(i);