From 38305fb1777147cbcb86774c466e88434daff141 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Fri, 14 Sep 2012 16:26:51 -0700 Subject: [PATCH] Data usage multi-user support. Switch to storing policy per-user instead of per-app, meaning each user has control over their own set of apps. Summarize the usage of non-current users. Only allow owner to make changes to overall network policy. Hide auto-sync menu when viewing app details. Search for MANAGE_NETWORK_USAGE intent across all package names sharing a UID. Bug: 7121279, 5419594, 6978663 Change-Id: Ia70f04df70d27da27faccb947cd27021c628a41a --- src/com/android/settings/DataUsageSummary.java | 122 ++++++++++++--------- src/com/android/settings/Settings.java | 1 + .../settings/applications/ManageApplications.java | 14 ++- .../android/settings/net/UidDetailProvider.java | 23 +++- 4 files changed, 104 insertions(+), 56 deletions(-) diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java index 9837193ac3..7c1832f692 100644 --- a/src/com/android/settings/DataUsageSummary.java +++ b/src/com/android/settings/DataUsageSummary.java @@ -48,6 +48,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.settings.Utils.prepareCustomPreferencesList; import android.animation.LayoutTransition; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -127,7 +128,6 @@ import android.widget.TabHost.TabSpec; import android.widget.TabWidget; import android.widget.TextView; -import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.settings.drawable.InsetBoundsDrawable; import com.android.settings.net.ChartData; @@ -447,20 +447,24 @@ public class DataUsageSummary extends Fragment { public void onPrepareOptionsMenu(Menu menu) { final Context context = getActivity(); final boolean appDetailMode = isAppDetailMode(); + final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming); mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode); mMenuDataRoaming.setChecked(getDataRoaming()); + mMenuDataRoaming.setVisible(isOwner); mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode); mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground()); + mMenuRestrictBackground.setVisible(isOwner); mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync); mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically()); + mMenuAutoSync.setVisible(isOwner && !appDetailMode); final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g); - split4g.setVisible(hasReadyMobile4gRadio(context) && !appDetailMode); + split4g.setVisible(hasReadyMobile4gRadio(context) && isOwner && !appDetailMode); split4g.setChecked(isMobilePolicySplit()); final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi); @@ -481,7 +485,7 @@ public class DataUsageSummary extends Fragment { final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered); if (hasReadyMobileRadio(context) || hasWifiRadio(context)) { - metered.setVisible(!appDetailMode); + metered.setVisible(isOwner && !appDetailMode); } else { metered.setVisible(false); } @@ -681,6 +685,7 @@ public class DataUsageSummary extends Fragment { final Context context = getActivity(); final String currentTab = mTabHost.getCurrentTabTag(); + final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; if (currentTab == null) { Log.w(TAG, "no tab selected; hiding body"); @@ -695,7 +700,7 @@ public class DataUsageSummary extends Fragment { if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab); - mDataEnabledView.setVisibility(View.VISIBLE); + mDataEnabledView.setVisibility(isOwner ? View.VISIBLE : View.GONE); // TODO: remove mobile tabs when SIM isn't ready final TelephonyManager tele = TelephonyManager.from(context); @@ -774,8 +779,8 @@ public class DataUsageSummary extends Fragment { mChart.bindNetworkPolicy(null); // show icon and all labels appearing under this app - final int appId = mCurrentApp.appId; - final UidDetail detail = mUidDetailProvider.getUidDetail(appId, true); + final int uid = mCurrentApp.key; + final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true); mAppIcon.setImageDrawable(detail.icon); mAppTitles.removeAllViews(); @@ -788,14 +793,21 @@ public class DataUsageSummary extends Fragment { } // enable settings button when package provides it - // TODO: target torwards entire UID instead of just first package - final String[] packageNames = pm.getPackagesForUid(appId); + final String[] packageNames = pm.getPackagesForUid(uid); if (packageNames != null && packageNames.length > 0) { mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); - mAppSettingsIntent.setPackage(packageNames[0]); mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); - final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null; + // Search for match across all packages + boolean matchFound = false; + for (String packageName : packageNames) { + mAppSettingsIntent.setPackage(packageName); + if (pm.resolveActivity(mAppSettingsIntent, 0) != null) { + matchFound = true; + break; + } + } + mAppSettings.setEnabled(matchFound); mAppSettings.setVisibility(View.VISIBLE); @@ -806,7 +818,7 @@ public class DataUsageSummary extends Fragment { updateDetailData(); - if (UserHandle.isApp(appId) && !mPolicyManager.getRestrictBackground() + if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground() && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) { setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); setPreferenceSummary(mAppRestrictView, @@ -855,7 +867,8 @@ public class DataUsageSummary extends Fragment { } private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { - return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked(); + return policy != null && isBandwidthControlEnabled() && mDataEnabled.isChecked() + && ActivityManager.getCurrentUser() == UserHandle.USER_OWNER; } private boolean isBandwidthControlEnabled() { @@ -886,16 +899,16 @@ public class DataUsageSummary extends Fragment { } private boolean getAppRestrictBackground() { - final int appId = mCurrentApp.appId; - final int uidPolicy = mPolicyManager.getAppPolicy(appId); + final int uid = mCurrentApp.key; + final int uidPolicy = mPolicyManager.getUidPolicy(uid); return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; } private void setAppRestrictBackground(boolean restrictBackground) { if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); - final int appId = mCurrentApp.appId; - mPolicyManager.setAppPolicy(appId, - restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); + final int uid = mCurrentApp.key; + mPolicyManager.setUidPolicy( + uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); mAppRestrict.setChecked(restrictBackground); } @@ -1080,7 +1093,7 @@ public class DataUsageSummary extends Fragment { // TODO: sigh, remove this hack once we understand 6450986 if (mUidDetailProvider == null || app == null) return; - final UidDetail detail = mUidDetailProvider.getUidDetail(app.appId, true); + final UidDetail detail = mUidDetailProvider.getUidDetail(app.key, true); AppDetailsFragment.show(DataUsageSummary.this, app, detail.label); } }; @@ -1224,9 +1237,9 @@ public class DataUsageSummary extends Fragment { @Override public void onLoadFinished(Loader loader, NetworkStats data) { - final int[] restrictedAppIds = mPolicyManager.getAppsWithPolicy( + final int[] restrictedUids = mPolicyManager.getUidsWithPolicy( POLICY_REJECT_METERED_BACKGROUND); - mAdapter.bindStats(data, restrictedAppIds); + mAdapter.bindStats(data, restrictedUids); updateEmptyVisible(); } @@ -1408,17 +1421,17 @@ public class DataUsageSummary extends Fragment { } public static class AppItem implements Comparable, Parcelable { - public final int appId; + public final int key; public boolean restricted; public SparseBooleanArray uids = new SparseBooleanArray(); public long total; - public AppItem(int appId) { - this.appId = appId; + public AppItem(int key) { + this.key = key; } public AppItem(Parcel parcel) { - appId = parcel.readInt(); + key = parcel.readInt(); uids = parcel.readSparseBooleanArray(); total = parcel.readLong(); } @@ -1429,7 +1442,7 @@ public class DataUsageSummary extends Fragment { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(appId); + dest.writeInt(key); dest.writeSparseBooleanArray(uids); dest.writeLong(total); } @@ -1475,49 +1488,56 @@ public class DataUsageSummary extends Fragment { /** * Bind the given {@link NetworkStats}, or {@code null} to clear list. */ - public void bindStats(NetworkStats stats, int[] restrictedAppIds) { + public void bindStats(NetworkStats stats, int[] restrictedUids) { mItems.clear(); - final AppItem systemItem = new AppItem(android.os.Process.SYSTEM_UID); - final SparseArray knownUids = new SparseArray(); + final int currentUserId = ActivityManager.getCurrentUser(); + final SparseArray knownItems = new SparseArray(); NetworkStats.Entry entry = null; final int size = stats != null ? stats.size() : 0; for (int i = 0; i < size; i++) { entry = stats.getValues(i, entry); - final boolean isApp = UserHandle.isApp(entry.uid); - final int appId = isApp ? UserHandle.getAppId(entry.uid) : entry.uid; - if (isApp || appId == UID_REMOVED || appId == UID_TETHERING) { - AppItem item = knownUids.get(appId); - if (item == null) { - item = new AppItem(appId); - knownUids.put(appId, item); - mItems.add(item); + // Decide how to collapse items together + final int uid = entry.uid; + final int collapseKey; + if (UserHandle.isApp(uid)) { + if (UserHandle.getUserId(uid) == currentUserId) { + collapseKey = uid; + } else { + collapseKey = UidDetailProvider.buildKeyForUser(UserHandle.getUserId(uid)); } - - item.total += entry.rxBytes + entry.txBytes; - item.addUid(entry.uid); + } else if (uid == UID_REMOVED || uid == UID_TETHERING) { + collapseKey = uid; } else { - systemItem.total += entry.rxBytes + entry.txBytes; - systemItem.addUid(entry.uid); + collapseKey = android.os.Process.SYSTEM_UID; } + + AppItem item = knownItems.get(collapseKey); + if (item == null) { + item = new AppItem(collapseKey); + mItems.add(item); + knownItems.put(item.key, item); + } + item.addUid(uid); + item.total += entry.rxBytes + entry.txBytes; } - for (int appId : restrictedAppIds) { - AppItem item = knownUids.get(appId); + for (int uid : restrictedUids) { + // Only splice in restricted state for current user + if (UserHandle.getUserId(uid) != currentUserId) continue; + + AppItem item = knownItems.get(uid); if (item == null) { - item = new AppItem(appId); + item = new AppItem(uid); item.total = -1; mItems.add(item); + knownItems.put(item.key, item); } item.restricted = true; } - if (systemItem.total > 0) { - mItems.add(systemItem); - } - Collections.sort(mItems); mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0; notifyDataSetChanged(); @@ -1535,7 +1555,7 @@ public class DataUsageSummary extends Fragment { @Override public long getItemId(int position) { - return mItems.get(position).appId; + return mItems.get(position).key; } @Override @@ -2126,7 +2146,7 @@ public class DataUsageSummary extends Fragment { existing.cancel(false); } - final UidDetail cachedDetail = provider.getUidDetail(item.appId, false); + final UidDetail cachedDetail = provider.getUidDetail(item.key, false); if (cachedDetail != null) { bindView(cachedDetail, target); } else { @@ -2155,7 +2175,7 @@ public class DataUsageSummary extends Fragment { @Override protected UidDetail doInBackground(Void... params) { - return mProvider.getUidDetail(mItem.appId, true); + return mProvider.getUidDetail(mItem.key, true); } @Override diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 88b3e87fdb..bf31386b47 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -100,6 +100,7 @@ public class Settings extends PreferenceActivity R.id.wireless_section, R.id.wifi_settings, R.id.bluetooth_settings, + R.id.data_usage_settings, R.id.device_section, R.id.sound_settings, R.id.display_settings, diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java index 1240d4349a..0a73b02d0f 100644 --- a/src/com/android/settings/applications/ManageApplications.java +++ b/src/com/android/settings/applications/ManageApplications.java @@ -20,6 +20,7 @@ import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Fragment; import android.app.INotificationManager; @@ -41,6 +42,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.preference.PreferenceActivity; import android.preference.PreferenceFrameLayout; import android.provider.Settings; @@ -1119,11 +1121,15 @@ public class ManageApplications extends Fragment implements + prefActivities.get(i).getPackageName()); pm.clearPackagePreferredActivities(prefActivities.get(i).getPackageName()); } - final int[] restrictedAppIds = npm.getAppsWithPolicy( + final int[] restrictedUids = npm.getUidsWithPolicy( POLICY_REJECT_METERED_BACKGROUND); - for (int i : restrictedAppIds) { - if (DEBUG) Log.v(TAG, "Clearing data policy: " + i); - npm.setAppPolicy(i, POLICY_NONE); + final int currentUserId = ActivityManager.getCurrentUser(); + for (int uid : restrictedUids) { + // Only reset for current user + if (UserHandle.getUserId(uid) == currentUserId) { + if (DEBUG) Log.v(TAG, "Clearing data policy: " + uid); + npm.setUidPolicy(uid, POLICY_NONE); + } } handler.post(new Runnable() { @Override public void run() { diff --git a/src/com/android/settings/net/UidDetailProvider.java b/src/com/android/settings/net/UidDetailProvider.java index dd2b8c09fe..37b99ddf17 100644 --- a/src/com/android/settings/net/UidDetailProvider.java +++ b/src/com/android/settings/net/UidDetailProvider.java @@ -21,20 +21,30 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.TrafficStats; +import android.os.UserManager; import android.text.TextUtils; import android.util.SparseArray; import com.android.settings.R; import com.android.settings.Utils; +/** + * Return details about a specific UID, handling special cases like + * {@link TrafficStats#UID_TETHERING} and {@link UserInfo}. + */ public class UidDetailProvider { private final Context mContext; private final SparseArray mUidDetailCache; + public static int buildKeyForUser(int userHandle) { + return -(2000 + userHandle); + } + public UidDetailProvider(Context context) { mContext = context.getApplicationContext(); mUidDetailCache = new SparseArray(); @@ -101,10 +111,21 @@ public class UidDetailProvider { return detail; } + // Handle keys that are actually user handles + if (uid <= -2000) { + final int userHandle = (-uid) - 2000; + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final UserInfo info = um.getUserInfo(userHandle); + if (info != null) { + detail.label = res.getString(R.string.running_process_item_user_label, info.name); + detail.icon = Drawable.createFromPath(info.iconPath); + return detail; + } + } + // otherwise fall back to using packagemanager labels final String[] packageNames = pm.getPackagesForUid(uid); final int length = packageNames != null ? packageNames.length : 0; - try { if (length == 1) { final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0); -- 2.11.0