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
This commit is contained in:
Jeff Sharkey
2012-09-14 16:26:51 -07:00
parent b2f4efb7a8
commit 38305fb177
4 changed files with 104 additions and 56 deletions

View File

@@ -48,6 +48,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.settings.Utils.prepareCustomPreferencesList; import static com.android.settings.Utils.prepareCustomPreferencesList;
import android.animation.LayoutTransition; import android.animation.LayoutTransition;
import android.app.ActivityManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
@@ -127,7 +128,6 @@ import android.widget.TabHost.TabSpec;
import android.widget.TabWidget; import android.widget.TabWidget;
import android.widget.TextView; import android.widget.TextView;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants;
import com.android.settings.drawable.InsetBoundsDrawable; import com.android.settings.drawable.InsetBoundsDrawable;
import com.android.settings.net.ChartData; import com.android.settings.net.ChartData;
@@ -447,20 +447,24 @@ public class DataUsageSummary extends Fragment {
public void onPrepareOptionsMenu(Menu menu) { public void onPrepareOptionsMenu(Menu menu) {
final Context context = getActivity(); final Context context = getActivity();
final boolean appDetailMode = isAppDetailMode(); final boolean appDetailMode = isAppDetailMode();
final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming); mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode); mMenuDataRoaming.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
mMenuDataRoaming.setChecked(getDataRoaming()); mMenuDataRoaming.setChecked(getDataRoaming());
mMenuDataRoaming.setVisible(isOwner);
mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background); mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode); mMenuRestrictBackground.setVisible(hasReadyMobileRadio(context) && !appDetailMode);
mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground()); mMenuRestrictBackground.setChecked(mPolicyManager.getRestrictBackground());
mMenuRestrictBackground.setVisible(isOwner);
mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync); mMenuAutoSync = menu.findItem(R.id.data_usage_menu_auto_sync);
mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically()); mMenuAutoSync.setChecked(ContentResolver.getMasterSyncAutomatically());
mMenuAutoSync.setVisible(isOwner && !appDetailMode);
final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g); 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()); split4g.setChecked(isMobilePolicySplit());
final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi); 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); final MenuItem metered = menu.findItem(R.id.data_usage_menu_metered);
if (hasReadyMobileRadio(context) || hasWifiRadio(context)) { if (hasReadyMobileRadio(context) || hasWifiRadio(context)) {
metered.setVisible(!appDetailMode); metered.setVisible(isOwner && !appDetailMode);
} else { } else {
metered.setVisible(false); metered.setVisible(false);
} }
@@ -681,6 +685,7 @@ public class DataUsageSummary extends Fragment {
final Context context = getActivity(); final Context context = getActivity();
final String currentTab = mTabHost.getCurrentTabTag(); final String currentTab = mTabHost.getCurrentTabTag();
final boolean isOwner = ActivityManager.getCurrentUser() == UserHandle.USER_OWNER;
if (currentTab == null) { if (currentTab == null) {
Log.w(TAG, "no tab selected; hiding body"); 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); 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 // TODO: remove mobile tabs when SIM isn't ready
final TelephonyManager tele = TelephonyManager.from(context); final TelephonyManager tele = TelephonyManager.from(context);
@@ -774,8 +779,8 @@ public class DataUsageSummary extends Fragment {
mChart.bindNetworkPolicy(null); mChart.bindNetworkPolicy(null);
// show icon and all labels appearing under this app // show icon and all labels appearing under this app
final int appId = mCurrentApp.appId; final int uid = mCurrentApp.key;
final UidDetail detail = mUidDetailProvider.getUidDetail(appId, true); final UidDetail detail = mUidDetailProvider.getUidDetail(uid, true);
mAppIcon.setImageDrawable(detail.icon); mAppIcon.setImageDrawable(detail.icon);
mAppTitles.removeAllViews(); mAppTitles.removeAllViews();
@@ -788,14 +793,21 @@ public class DataUsageSummary extends Fragment {
} }
// enable settings button when package provides it // enable settings button when package provides it
// TODO: target torwards entire UID instead of just first package final String[] packageNames = pm.getPackagesForUid(uid);
final String[] packageNames = pm.getPackagesForUid(appId);
if (packageNames != null && packageNames.length > 0) { if (packageNames != null && packageNames.length > 0) {
mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE); mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
mAppSettingsIntent.setPackage(packageNames[0]);
mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); 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.setEnabled(matchFound);
mAppSettings.setVisibility(View.VISIBLE); mAppSettings.setVisibility(View.VISIBLE);
@@ -806,7 +818,7 @@ public class DataUsageSummary extends Fragment {
updateDetailData(); updateDetailData();
if (UserHandle.isApp(appId) && !mPolicyManager.getRestrictBackground() if (UserHandle.isApp(uid) && !mPolicyManager.getRestrictBackground()
&& isBandwidthControlEnabled() && hasReadyMobileRadio(context)) { && isBandwidthControlEnabled() && hasReadyMobileRadio(context)) {
setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background); setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
setPreferenceSummary(mAppRestrictView, setPreferenceSummary(mAppRestrictView,
@@ -855,7 +867,8 @@ public class DataUsageSummary extends Fragment {
} }
private boolean isNetworkPolicyModifiable(NetworkPolicy policy) { 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() { private boolean isBandwidthControlEnabled() {
@@ -886,16 +899,16 @@ public class DataUsageSummary extends Fragment {
} }
private boolean getAppRestrictBackground() { private boolean getAppRestrictBackground() {
final int appId = mCurrentApp.appId; final int uid = mCurrentApp.key;
final int uidPolicy = mPolicyManager.getAppPolicy(appId); final int uidPolicy = mPolicyManager.getUidPolicy(uid);
return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0; return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
} }
private void setAppRestrictBackground(boolean restrictBackground) { private void setAppRestrictBackground(boolean restrictBackground) {
if (LOGD) Log.d(TAG, "setAppRestrictBackground()"); if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
final int appId = mCurrentApp.appId; final int uid = mCurrentApp.key;
mPolicyManager.setAppPolicy(appId, mPolicyManager.setUidPolicy(
restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE); uid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
mAppRestrict.setChecked(restrictBackground); mAppRestrict.setChecked(restrictBackground);
} }
@@ -1080,7 +1093,7 @@ public class DataUsageSummary extends Fragment {
// TODO: sigh, remove this hack once we understand 6450986 // TODO: sigh, remove this hack once we understand 6450986
if (mUidDetailProvider == null || app == null) return; 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); AppDetailsFragment.show(DataUsageSummary.this, app, detail.label);
} }
}; };
@@ -1224,9 +1237,9 @@ public class DataUsageSummary extends Fragment {
@Override @Override
public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
final int[] restrictedAppIds = mPolicyManager.getAppsWithPolicy( final int[] restrictedUids = mPolicyManager.getUidsWithPolicy(
POLICY_REJECT_METERED_BACKGROUND); POLICY_REJECT_METERED_BACKGROUND);
mAdapter.bindStats(data, restrictedAppIds); mAdapter.bindStats(data, restrictedUids);
updateEmptyVisible(); updateEmptyVisible();
} }
@@ -1408,17 +1421,17 @@ public class DataUsageSummary extends Fragment {
} }
public static class AppItem implements Comparable<AppItem>, Parcelable { public static class AppItem implements Comparable<AppItem>, Parcelable {
public final int appId; public final int key;
public boolean restricted; public boolean restricted;
public SparseBooleanArray uids = new SparseBooleanArray(); public SparseBooleanArray uids = new SparseBooleanArray();
public long total; public long total;
public AppItem(int appId) { public AppItem(int key) {
this.appId = appId; this.key = key;
} }
public AppItem(Parcel parcel) { public AppItem(Parcel parcel) {
appId = parcel.readInt(); key = parcel.readInt();
uids = parcel.readSparseBooleanArray(); uids = parcel.readSparseBooleanArray();
total = parcel.readLong(); total = parcel.readLong();
} }
@@ -1429,7 +1442,7 @@ public class DataUsageSummary extends Fragment {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(appId); dest.writeInt(key);
dest.writeSparseBooleanArray(uids); dest.writeSparseBooleanArray(uids);
dest.writeLong(total); dest.writeLong(total);
} }
@@ -1475,49 +1488,56 @@ public class DataUsageSummary extends Fragment {
/** /**
* Bind the given {@link NetworkStats}, or {@code null} to clear list. * 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(); mItems.clear();
final AppItem systemItem = new AppItem(android.os.Process.SYSTEM_UID); final int currentUserId = ActivityManager.getCurrentUser();
final SparseArray<AppItem> knownUids = new SparseArray<AppItem>(); final SparseArray<AppItem> knownItems = new SparseArray<AppItem>();
NetworkStats.Entry entry = null; NetworkStats.Entry entry = null;
final int size = stats != null ? stats.size() : 0; final int size = stats != null ? stats.size() : 0;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
entry = stats.getValues(i, entry); entry = stats.getValues(i, entry);
final boolean isApp = UserHandle.isApp(entry.uid); // Decide how to collapse items together
final int appId = isApp ? UserHandle.getAppId(entry.uid) : entry.uid; final int uid = entry.uid;
if (isApp || appId == UID_REMOVED || appId == UID_TETHERING) { final int collapseKey;
AppItem item = knownUids.get(appId); if (UserHandle.isApp(uid)) {
if (item == null) { if (UserHandle.getUserId(uid) == currentUserId) {
item = new AppItem(appId); collapseKey = uid;
knownUids.put(appId, item); } else {
mItems.add(item); collapseKey = UidDetailProvider.buildKeyForUser(UserHandle.getUserId(uid));
} }
} else if (uid == UID_REMOVED || uid == UID_TETHERING) {
item.total += entry.rxBytes + entry.txBytes; collapseKey = uid;
item.addUid(entry.uid);
} else { } else {
systemItem.total += entry.rxBytes + entry.txBytes; collapseKey = android.os.Process.SYSTEM_UID;
systemItem.addUid(entry.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) { for (int uid : restrictedUids) {
AppItem item = knownUids.get(appId); // Only splice in restricted state for current user
if (UserHandle.getUserId(uid) != currentUserId) continue;
AppItem item = knownItems.get(uid);
if (item == null) { if (item == null) {
item = new AppItem(appId); item = new AppItem(uid);
item.total = -1; item.total = -1;
mItems.add(item); mItems.add(item);
knownItems.put(item.key, item);
} }
item.restricted = true; item.restricted = true;
} }
if (systemItem.total > 0) {
mItems.add(systemItem);
}
Collections.sort(mItems); Collections.sort(mItems);
mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0; mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
notifyDataSetChanged(); notifyDataSetChanged();
@@ -1535,7 +1555,7 @@ public class DataUsageSummary extends Fragment {
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
return mItems.get(position).appId; return mItems.get(position).key;
} }
@Override @Override
@@ -2126,7 +2146,7 @@ public class DataUsageSummary extends Fragment {
existing.cancel(false); existing.cancel(false);
} }
final UidDetail cachedDetail = provider.getUidDetail(item.appId, false); final UidDetail cachedDetail = provider.getUidDetail(item.key, false);
if (cachedDetail != null) { if (cachedDetail != null) {
bindView(cachedDetail, target); bindView(cachedDetail, target);
} else { } else {
@@ -2155,7 +2175,7 @@ public class DataUsageSummary extends Fragment {
@Override @Override
protected UidDetail doInBackground(Void... params) { protected UidDetail doInBackground(Void... params) {
return mProvider.getUidDetail(mItem.appId, true); return mProvider.getUidDetail(mItem.key, true);
} }
@Override @Override

View File

@@ -100,6 +100,7 @@ public class Settings extends PreferenceActivity
R.id.wireless_section, R.id.wireless_section,
R.id.wifi_settings, R.id.wifi_settings,
R.id.bluetooth_settings, R.id.bluetooth_settings,
R.id.data_usage_settings,
R.id.device_section, R.id.device_section,
R.id.sound_settings, R.id.sound_settings,
R.id.display_settings, R.id.display_settings,

View File

@@ -20,6 +20,7 @@ import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.INotificationManager; import android.app.INotificationManager;
@@ -41,6 +42,7 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceFrameLayout; import android.preference.PreferenceFrameLayout;
import android.provider.Settings; import android.provider.Settings;
@@ -1119,11 +1121,15 @@ public class ManageApplications extends Fragment implements
+ prefActivities.get(i).getPackageName()); + prefActivities.get(i).getPackageName());
pm.clearPackagePreferredActivities(prefActivities.get(i).getPackageName()); pm.clearPackagePreferredActivities(prefActivities.get(i).getPackageName());
} }
final int[] restrictedAppIds = npm.getAppsWithPolicy( final int[] restrictedUids = npm.getUidsWithPolicy(
POLICY_REJECT_METERED_BACKGROUND); POLICY_REJECT_METERED_BACKGROUND);
for (int i : restrictedAppIds) { final int currentUserId = ActivityManager.getCurrentUser();
if (DEBUG) Log.v(TAG, "Clearing data policy: " + i); for (int uid : restrictedUids) {
npm.setAppPolicy(i, POLICY_NONE); // 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() { handler.post(new Runnable() {
@Override public void run() { @Override public void run() {

View File

@@ -21,20 +21,30 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.TrafficStats; import android.net.TrafficStats;
import android.os.UserManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; 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 { public class UidDetailProvider {
private final Context mContext; private final Context mContext;
private final SparseArray<UidDetail> mUidDetailCache; private final SparseArray<UidDetail> mUidDetailCache;
public static int buildKeyForUser(int userHandle) {
return -(2000 + userHandle);
}
public UidDetailProvider(Context context) { public UidDetailProvider(Context context) {
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
mUidDetailCache = new SparseArray<UidDetail>(); mUidDetailCache = new SparseArray<UidDetail>();
@@ -101,10 +111,21 @@ public class UidDetailProvider {
return detail; 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 // otherwise fall back to using packagemanager labels
final String[] packageNames = pm.getPackagesForUid(uid); final String[] packageNames = pm.getPackagesForUid(uid);
final int length = packageNames != null ? packageNames.length : 0; final int length = packageNames != null ? packageNames.length : 0;
try { try {
if (length == 1) { if (length == 1) {
final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0); final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);