platform-packages-apps-Settings / src / com / android / settings / datausage / UnrestrictedDataAccess.java
UnrestrictedDataAccess.java
Raw
/*
 * Copyright (C) 2016 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.datausage;

import static com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted;

import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.AppSwitchPreference;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedPreferenceHelper;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;

import java.util.ArrayList;

public class UnrestrictedDataAccess extends SettingsPreferenceFragment
        implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
        Preference.OnPreferenceChangeListener {

    private static final int MENU_SHOW_SYSTEM = Menu.FIRST + 42;
    private static final String EXTRA_SHOW_SYSTEM = "show_system";

    private ApplicationsState mApplicationsState;
    private AppStateDataUsageBridge mDataUsageBridge;
    private ApplicationsState.Session mSession;
    private DataSaverBackend mDataSaverBackend;
    private boolean mShowSystem;
    private boolean mExtraLoaded;
    private AppFilter mFilter;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setAnimationAllowed(true);
        mApplicationsState = ApplicationsState.getInstance(
                (Application) getContext().getApplicationContext());
        mDataSaverBackend = new DataSaverBackend(getContext());
        mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
        mSession = mApplicationsState.newSession(this, getLifecycle());
        mShowSystem = icicle != null && icicle.getBoolean(EXTRA_SHOW_SYSTEM);
        mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED
                : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER;
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
                mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_SHOW_SYSTEM:
                mShowSystem = !mShowSystem;
                item.setTitle(mShowSystem ? R.string.menu_hide_system : R.string.menu_show_system);
                mFilter = mShowSystem ? ApplicationsState.FILTER_ALL_ENABLED
                        : ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER;
                if (mExtraLoaded) {
                    rebuild();
                }
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setLoading(true, false);
    }

    @Override
    public void onResume() {
        super.onResume();
        mDataUsageBridge.resume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mDataUsageBridge.pause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mDataUsageBridge.release();
    }

    @Override
    public void onExtraInfoUpdated() {
        mExtraLoaded = true;
        rebuild();
    }

    @Override
    public int getHelpResource() {
        return R.string.help_url_unrestricted_data_access;
    }

    private void rebuild() {
        ArrayList<AppEntry> apps = mSession.rebuild(mFilter, ApplicationsState.ALPHA_COMPARATOR);
        if (apps != null) {
            onRebuildComplete(apps);
        }
    }

    @Override
    public void onRunningStateChanged(boolean running) {

    }

    @Override
    public void onPackageListChanged() {

    }

    @Override
    public void onRebuildComplete(ArrayList<AppEntry> apps) {
        if (getContext() == null) return;
        cacheRemoveAllPrefs(getPreferenceScreen());
        final int N = apps.size();
        for (int i = 0; i < N; i++) {
            AppEntry entry = apps.get(i);
            if (!shouldAddPreference(entry)) {
                continue;
            }
            String key = entry.info.packageName + "|" + entry.info.uid;
            AccessPreference preference = (AccessPreference) getCachedPreference(key);
            if (preference == null) {
                preference = new AccessPreference(getPrefContext(), entry);
                preference.setKey(key);
                preference.setOnPreferenceChangeListener(this);
                getPreferenceScreen().addPreference(preference);
            } else {
                preference.setDisabledByAdmin(checkIfMeteredDataRestricted(getContext(),
                        entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
                preference.reuse();
            }
            preference.setOrder(i);
        }
        setLoading(false, true);
        removeCachedPrefs(getPreferenceScreen());
    }

    @Override
    public void onPackageIconChanged() {

    }

    @Override
    public void onPackageSizeChanged(String packageName) {

    }

    @Override
    public void onAllSizesComputed() {

    }

    @Override
    public void onLauncherInfoChanged() {

    }

    @Override
    public void onLoadEntriesCompleted() {

    }

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.DATA_USAGE_UNRESTRICTED_ACCESS;
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.unrestricted_data_access_settings;
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (preference instanceof AccessPreference) {
            AccessPreference accessPreference = (AccessPreference) preference;
            boolean whitelisted = newValue == Boolean.TRUE;
            logSpecialPermissionChange(whitelisted, accessPreference.mEntry.info.packageName);
            mDataSaverBackend.setIsWhitelisted(accessPreference.mEntry.info.uid,
                    accessPreference.mEntry.info.packageName, whitelisted);
            accessPreference.mState.isDataSaverWhitelisted = whitelisted;
            return true;
        }
        return false;
    }

    @VisibleForTesting
    void logSpecialPermissionChange(boolean whitelisted, String packageName) {
        int logCategory = whitelisted ? MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_ALLOW
                : MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY;
        FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider().action(getContext(),
                logCategory, packageName);
    }

    @VisibleForTesting
    boolean shouldAddPreference(AppEntry app) {
        return app != null && UserHandle.isApp(app.info.uid);
    }

    @VisibleForTesting
    class AccessPreference extends AppSwitchPreference
            implements DataSaverBackend.Listener {
        private final AppEntry mEntry;
        private final DataUsageState mState;
        private final RestrictedPreferenceHelper mHelper;

        public AccessPreference(final Context context, AppEntry entry) {
            super(context);
            setWidgetLayoutResource(R.layout.restricted_switch_widget);
            mHelper = new RestrictedPreferenceHelper(context, this, null);
            mEntry = entry;
            mState = (DataUsageState) mEntry.extraInfo;
            mEntry.ensureLabel(getContext());
            setDisabledByAdmin(checkIfMeteredDataRestricted(context, entry.info.packageName,
                    UserHandle.getUserId(entry.info.uid)));
            setState();
            if (mEntry.icon != null) {
                setIcon(mEntry.icon);
            }
        }

        @Override
        public void onAttached() {
            super.onAttached();
            mDataSaverBackend.addListener(this);
        }

        @Override
        public void onDetached() {
            mDataSaverBackend.remListener(this);
            super.onDetached();
        }

        @Override
        protected void onClick() {
            if (mState.isDataSaverBlacklisted) {
                // app is blacklisted, launch App Data Usage screen
                AppInfoDashboardFragment.startAppInfoFragment(AppDataUsage.class,
                    R.string.app_data_usage,
                    null /* arguments */,
                    UnrestrictedDataAccess.this,
                    mEntry);
            } else {
                // app is not blacklisted, let superclass handle toggle switch
                super.onClick();
            }
        }

        @Override
        public void performClick() {
            if (!mHelper.performClick()) {
                super.performClick();
            }
        }

        // Sets UI state based on whitelist/blacklist status.
        private void setState() {
            setTitle(mEntry.label);
            if (mState != null) {
                setChecked(mState.isDataSaverWhitelisted);
                if (isDisabledByAdmin()) {
                    setSummary(R.string.disabled_by_admin);
                } else if (mState.isDataSaverBlacklisted) {
                    setSummary(R.string.restrict_background_blacklisted);
                } else {
                    setSummary("");
                }
            }
        }

        public void reuse() {
            setState();
            notifyChanged();
        }

        @Override
        public void onBindViewHolder(PreferenceViewHolder holder) {
            if (mEntry.icon == null) {
                holder.itemView.post(new Runnable() {
                    @Override
                    public void run() {
                        // Ensure we have an icon before binding.
                        mApplicationsState.ensureIcon(mEntry);
                        // This might trigger us to bind again, but it gives an easy way to only
                        // load the icon once its needed, so its probably worth it.
                        setIcon(mEntry.icon);
                    }
                });
            }
            final boolean disabledByAdmin = isDisabledByAdmin();
            final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
            if (disabledByAdmin) {
                widgetFrame.setVisibility(View.VISIBLE);
            } else {
                widgetFrame.setVisibility(mState != null && mState.isDataSaverBlacklisted
                        ? View.INVISIBLE : View.VISIBLE);
            }
            super.onBindViewHolder(holder);

            mHelper.onBindViewHolder(holder);
            holder.findViewById(R.id.restricted_icon).setVisibility(
                    disabledByAdmin ? View.VISIBLE : View.GONE);
            holder.findViewById(android.R.id.switch_widget).setVisibility(
                    disabledByAdmin ? View.GONE : View.VISIBLE);
        }

        @Override
        public void onDataSaverChanged(boolean isDataSaving) {
        }

        @Override
        public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
            if (mState != null && mEntry.info.uid == uid) {
                mState.isDataSaverWhitelisted = isWhitelisted;
                reuse();
            }
        }

        @Override
        public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
            if (mState != null && mEntry.info.uid == uid) {
                mState.isDataSaverBlacklisted = isBlacklisted;
                reuse();
            }
        }

        public void setDisabledByAdmin(EnforcedAdmin admin) {
            mHelper.setDisabledByAdmin(admin);
        }

        public boolean isDisabledByAdmin() {
            return mHelper.isDisabledByAdmin();
        }

        @VisibleForTesting
        public AppEntry getEntryForTest() {
            return mEntry;
        }
    }

}