Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 38305fb1 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent b2f4efb7
Loading
Loading
Loading
Loading
+71 −51
Original line number Diff line number Diff line
@@ -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<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 class DataUsageSummary extends Fragment {
    }

    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 @@ 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<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);
                // 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));
                    }
                } else if (uid == UID_REMOVED || uid == UID_TETHERING) {
                    collapseKey = uid;
                } else {
                    collapseKey = android.os.Process.SYSTEM_UID;
                }

                AppItem item = knownItems.get(collapseKey);
                if (item == null) {
                        item = new AppItem(appId);
                        knownUids.put(appId, item);
                    item = new AppItem(collapseKey);
                    mItems.add(item);
                    knownItems.put(item.key, item);
                }

                item.addUid(uid);
                item.total += entry.rxBytes + entry.txBytes;
                    item.addUid(entry.uid);
                } else {
                    systemItem.total += entry.rxBytes + entry.txBytes;
                    systemItem.addUid(entry.uid);
                }
            }

            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
+1 −0
Original line number Diff line number Diff line
@@ -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,
+10 −4
Original line number Diff line number Diff line
@@ -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() {
+22 −1
Original line number Diff line number Diff line
@@ -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<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 @@ 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);