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
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
index 9837193..7c1832f6 100644
--- a/src/com/android/settings/DataUsageSummary.java
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -48,6 +48,7 @@
 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.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 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 @@
 
         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 @@
 
         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 @@
 
         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 @@
         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 @@
         }
 
         // 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 @@
 
         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 @@
     }
 
     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 @@
     }
 
     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 @@
             // 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 @@
 
         @Override
         public void onLoadFinished(Loader<NetworkStats> 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 static class AppItem implements Comparable<AppItem>, 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 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(appId);
+            dest.writeInt(key);
             dest.writeSparseBooleanArray(uids);
             dest.writeLong(total);
         }
@@ -1475,49 +1488,56 @@
         /**
          * 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<AppItem> knownUids = new SparseArray<AppItem>();
+            final int currentUserId = ActivityManager.getCurrentUser();
+            final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
 
             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 @@
 
         @Override
         public long getItemId(int position) {
-            return mItems.get(position).appId;
+            return mItems.get(position).key;
         }
 
         @Override
@@ -2126,7 +2146,7 @@
                 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 @@
 
         @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 88b3e87..bf31386 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -100,6 +100,7 @@
             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 1240d43..0a73b02 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_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.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 @@
                                 + 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 dd2b8c0..37b99dd 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.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<UidDetail> mUidDetailCache;
 
+    public static int buildKeyForUser(int userHandle) {
+        return -(2000 + userHandle);
+    }
+
     public UidDetailProvider(Context context) {
         mContext = context.getApplicationContext();
         mUidDetailCache = new SparseArray<UidDetail>();
@@ -101,10 +111,21 @@
                 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);