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

Commit 2c18bbbd authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Allow app lists to have a toggle" into pi-dev

parents 0a5367c0 1cda00b1
Loading
Loading
Loading
Loading
+64 −2
Original line number Diff line number Diff line
@@ -20,8 +20,12 @@ import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Switch;

import com.android.settings.R;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -37,13 +41,18 @@ import java.util.Map;
 */
public class AppStateNotificationBridge extends AppStateBaseBridge {

    private final Context mContext;
    private UsageStatsManager mUsageStatsManager;
    private NotificationBackend mBackend;
    private static final int DAYS_TO_CHECK = 7;

    public AppStateNotificationBridge(ApplicationsState appState,
            Callback callback, UsageStatsManager usageStatsManager) {
    public AppStateNotificationBridge(Context context, ApplicationsState appState,
            Callback callback, UsageStatsManager usageStatsManager,
            NotificationBackend backend) {
        super(appState, callback);
        mContext = context;
        mUsageStatsManager = usageStatsManager;
        mBackend = backend;
    }

    @Override
@@ -55,6 +64,7 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        for (AppEntry entry : apps) {
            NotificationsSentState stats = map.get(entry.info.packageName);
            calculateAvgSentCounts(stats);
            addBlockStatus(entry, stats);
            entry.extraInfo = stats;
        }
    }
@@ -64,6 +74,7 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        Map<String, NotificationsSentState> map = getAggregatedUsageEvents();
        NotificationsSentState stats = map.get(entry.info.packageName);
        calculateAvgSentCounts(stats);
        addBlockStatus(entry, stats);
        entry.extraInfo = stats;
    }

@@ -83,6 +94,14 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        }
    }

    private void addBlockStatus(AppEntry entry, NotificationsSentState stats) {
        if (stats != null) {
            stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid);
            stats.systemApp = mBackend.isSystemApp(mContext, entry.info);
            stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked);
        }
    }

    private void calculateAvgSentCounts(NotificationsSentState stats) {
        if (stats != null) {
            stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK);
@@ -130,6 +149,28 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        return null;
    }

    public View.OnClickListener getSwitchOnClickListener(final AppEntry entry) {
        if (entry != null) {
            return v -> {
                ViewGroup view = (ViewGroup) v;
                Switch toggle = view.findViewById(R.id.switchWidget);
                if (toggle != null) {
                    if (!toggle.isEnabled()) {
                        return;
                    }
                    toggle.toggle();
                    mBackend.setNotificationsEnabledForPackage(
                            entry.info.packageName, entry.info.uid, toggle.isChecked());
                    NotificationsSentState stats = getNotificationsSentState(entry);
                    if (stats != null) {
                        stats.blocked = !toggle.isChecked();
                    }
                }
            };
        }
        return null;
    }

    public static final AppFilter FILTER_APP_NOTIFICATION_RECENCY = new AppFilter() {
        @Override
        public void init() {
@@ -192,6 +233,24 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        }
    };

    public static final boolean enableSwitch(AppEntry entry) {
        NotificationsSentState stats = getNotificationsSentState(entry);
        if (stats == null) {
            return false;
        }

        return stats.blockable;
    }

    public static final boolean checkSwitch(AppEntry entry) {
        NotificationsSentState stats = getNotificationsSentState(entry);
        if (stats == null) {
            return false;
        }

        return !stats.blocked;
    }

    /**
     * NotificationsSentState contains how often an app sends notifications and how recently it sent
     * one.
@@ -201,5 +260,8 @@ public class AppStateNotificationBridge extends AppStateBaseBridge {
        public int avgSentWeekly = 0;
        public long lastSent = 0;
        public int sentCount = 0;
        public boolean blockable;
        public boolean blocked;
        public boolean systemApp;
    }
}
+33 −2
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;

import com.android.settings.R;
@@ -47,7 +48,10 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
    final TextView mSummary;
    @VisibleForTesting
    final TextView mDisabled;

    @VisibleForTesting
    final ViewGroup mWidgetContainer;
    @VisibleForTesting
    final Switch mSwitch;

    ApplicationViewHolder(View itemView, boolean keepStableHeight) {
        super(itemView);
@@ -57,11 +61,30 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
        mSummary = itemView.findViewById(android.R.id.summary);
        mDisabled = itemView.findViewById(R.id.appendix);
        mKeepStableHeight = keepStableHeight;
        mSwitch = itemView.findViewById(R.id.switchWidget);
        mWidgetContainer = itemView.findViewById(android.R.id.widget_frame);
    }

    static View newView(ViewGroup parent) {
        return LayoutInflater.from(parent.getContext())
        return newView(parent, false);
    }

    static View newView(ViewGroup parent, boolean twoTarget) {
        ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.preference_app, parent, false);
        if (twoTarget) {
            final ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
            if (widgetFrame != null) {
               LayoutInflater.from(parent.getContext())
                       .inflate(R.layout.preference_widget_master_switch, widgetFrame, true);

               View divider = LayoutInflater.from(parent.getContext()).inflate(
                       R.layout.preference_two_target_divider, view, false);
               // second to last, before widget frame
               view.addView(divider, view.getChildCount() - 1);
            }
        }
        return view;
    }

    void setSummary(CharSequence summary) {
@@ -141,4 +164,12 @@ public class ApplicationViewHolder extends RecyclerView.ViewHolder {
            setSummary(invalidSizeStr);
        }
    }

    void updateSwitch(View.OnClickListener listener, boolean enabled, boolean checked) {
        if (mSwitch != null && mWidgetContainer != null) {
            mWidgetContainer.setOnClickListener(listener);
            mSwitch.setChecked(checked);
            mSwitch.setEnabled(enabled);
        }
    }
}
+31 −3
Original line number Diff line number Diff line
@@ -109,6 +109,7 @@ import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.fuelgauge.HighPowerDetail;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.widget.LoadingViewController;
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
import com.android.settings.wifi.ChangeWifiStateDetails;
@@ -223,6 +224,7 @@ public class ManageApplications extends InstrumentedFragment
    private Spinner mFilterSpinner;
    private FilterSpinnerAdapter mFilterAdapter;
    private UsageStatsManager mUsageStatsManager;
    private NotificationBackend mNotificationBackend;
    private ResetAppsHelper mResetAppsHelper;
    private String mVolumeUuid;
    private int mStorageType;
@@ -292,6 +294,7 @@ public class ManageApplications extends InstrumentedFragment
            mListType = LIST_TYPE_NOTIFICATION;
            mUsageStatsManager =
                    (UsageStatsManager) getContext().getSystemService(Context.USAGE_STATS_SERVICE);
            mNotificationBackend = new NotificationBackend();
            mSortOrder = R.id.sort_order_recent_notification;
            screenTitle = R.string.app_notifications_title;
        } else {
@@ -869,8 +872,9 @@ public class ManageApplications extends InstrumentedFragment
            mContext = manageApplications.getActivity();
            mAppFilter = appFilter;
            if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
                mExtraInfoBridge = new AppStateNotificationBridge(mState, this,
                        manageApplications.mUsageStatsManager);
                mExtraInfoBridge = new AppStateNotificationBridge(mContext, mState, this,
                        manageApplications.mUsageStatsManager,
                        manageApplications.mNotificationBackend);
            } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
                mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
            } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
@@ -988,7 +992,12 @@ public class ManageApplications extends InstrumentedFragment

        @Override
        public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            final View view = ApplicationViewHolder.newView(parent);
            View view;
            if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
                view = ApplicationViewHolder.newView(parent, true /* twoTarget */);
            } else {
                view = ApplicationViewHolder.newView(parent, false /* twoTarget */);
            }
            return new ApplicationViewHolder(view,
                    shouldUseStableItemHeight(mManageApplications.mListType));
        }
@@ -1276,6 +1285,7 @@ public class ManageApplications extends InstrumentedFragment
                    mState.ensureIcon(entry);
                    holder.setIcon(entry.icon);
                    updateSummary(holder, entry);
                    updateSwitch(holder, entry);
                    holder.updateDisableView(entry.info);
                }
                holder.setEnabled(isEnabled(position));
@@ -1328,6 +1338,24 @@ public class ManageApplications extends InstrumentedFragment
            }
        }

        private void updateSwitch(ApplicationViewHolder holder, AppEntry entry) {
            switch (mManageApplications.mListType) {
                case LIST_TYPE_NOTIFICATION:
                    holder.updateSwitch(((AppStateNotificationBridge) mExtraInfoBridge)
                                    .getSwitchOnClickListener(entry),
                            AppStateNotificationBridge.enableSwitch(entry),
                            AppStateNotificationBridge.checkSwitch(entry));
                    if (entry.extraInfo != null) {
                        holder.setSummary(AppStateNotificationBridge.getSummary(mContext,
                                (NotificationsSentState) entry.extraInfo,
                                (mLastSortMode == R.id.sort_order_recent_notification)));
                    } else {
                        holder.setSummary(null);
                    }
                    break;
            }
        }

        private boolean hasExtraView() {
            return mExtraViewController != null
                    && mExtraViewController.shouldShow();
+19 −2
Original line number Diff line number Diff line
@@ -65,11 +65,15 @@ public class NotificationBackend {

    public AppRow loadAppRow(Context context, PackageManager pm, PackageInfo app) {
        final AppRow row = loadAppRow(context, pm, app.applicationInfo);
        recordCanBeBlocked(context, pm, app, row);
        return row;
    }

    void recordCanBeBlocked(Context context, PackageManager pm, PackageInfo app, AppRow row) {
        row.systemApp = Utils.isSystemPackage(context.getResources(), pm, app);
        final String[] nonBlockablePkgs = context.getResources().getStringArray(
                com.android.internal.R.array.config_nonBlockableNotificationPackages);
        markAppRowWithBlockables(nonBlockablePkgs, row, app.packageName);
        return row;
    }

    @VisibleForTesting static void markAppRowWithBlockables(String[] nonBlockablePkgs, AppRow row,
@@ -92,6 +96,19 @@ public class NotificationBackend {
        }
    }

    public boolean isSystemApp(Context context, ApplicationInfo app) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    app.packageName, PackageManager.GET_SIGNATURES);
            final AppRow row = new AppRow();
            recordCanBeBlocked(context,  context.getPackageManager(), info, row);
            return row.systemApp;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean getNotificationsBanned(String pkg, int uid) {
        try {
            final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid);
+47 −2
Original line number Diff line number Diff line
@@ -33,8 +33,11 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.usage.UsageEvents;
@@ -44,9 +47,12 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Looper;
import android.os.Parcel;
import android.view.ViewGroup;
import android.widget.Switch;

import com.android.settings.R;
import com.android.settings.applications.AppStateNotificationBridge.NotificationsSentState;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -74,6 +80,8 @@ public class AppStateNotificationBridgeTest {
    private ApplicationsState mState;
    @Mock
    private UsageStatsManager mUsageStats;
    @Mock
    private NotificationBackend mBackend;
    private Context mContext;
    private AppStateNotificationBridge mBridge;

@@ -82,10 +90,12 @@ public class AppStateNotificationBridgeTest {
        MockitoAnnotations.initMocks(this);
        when(mState.newSession(any())).thenReturn(mSession);
        when(mState.getBackgroundLooper()).thenReturn(mock(Looper.class));
        when(mBackend.getNotificationsBanned(anyString(), anyInt())).thenReturn(true);
        when(mBackend.isSystemApp(any(), any())).thenReturn(true);
        mContext = RuntimeEnvironment.application.getApplicationContext();

        mBridge = new AppStateNotificationBridge(mState,
                mock(AppStateBaseBridge.Callback.class), mUsageStats);
        mBridge = new AppStateNotificationBridge(mContext, mState,
                mock(AppStateBaseBridge.Callback.class), mUsageStats, mBackend);
    }

    private AppEntry getMockAppEntry(String pkg) {
@@ -213,6 +223,9 @@ public class AppStateNotificationBridgeTest {
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).lastSent).isEqualTo(6);
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentDaily).isEqualTo(1);
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).avgSentWeekly).isEqualTo(0);
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).blocked).isTrue();
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).systemApp).isTrue();
        assertThat(((NotificationsSentState) apps.get(0).extraInfo).blockable).isTrue();
    }

    @Test
@@ -281,6 +294,9 @@ public class AppStateNotificationBridgeTest {
        assertThat(((NotificationsSentState) entry.extraInfo).lastSent).isEqualTo(12);
        assertThat(((NotificationsSentState) entry.extraInfo).avgSentDaily).isEqualTo(2);
        assertThat(((NotificationsSentState) entry.extraInfo).avgSentWeekly).isEqualTo(0);
        assertThat(((NotificationsSentState) entry.extraInfo).blocked).isTrue();
        assertThat(((NotificationsSentState) entry.extraInfo).systemApp).isTrue();
        assertThat(((NotificationsSentState) entry.extraInfo).blockable).isTrue();
    }

    @Test
@@ -410,4 +426,33 @@ public class AppStateNotificationBridgeTest {
        assertThat(entries).containsExactly(veryFrequentDailyEntry, notFrequentDailyEntry,
                veryFrequentWeeklyEntry, notFrequentWeeklyEntry);
    }

    @Test
    public void testSwitchOnClickListener() {
        ViewGroup parent = mock(ViewGroup.class);
        Switch toggle = mock(Switch.class);
        when(toggle.isChecked()).thenReturn(true);
        when(toggle.isEnabled()).thenReturn(true);
        when(parent.findViewById(anyInt())).thenReturn(toggle);

        AppEntry entry = mock(AppEntry.class);
        entry.info = new ApplicationInfo();
        entry.info.packageName = "pkg";
        entry.info.uid = 1356;
        entry.extraInfo = new NotificationsSentState();

        ViewGroup.OnClickListener listener = mBridge.getSwitchOnClickListener(entry);
        listener.onClick(parent);

        verify(toggle).toggle();
        verify(mBackend).setNotificationsEnabledForPackage(
                entry.info.packageName, entry.info.uid, true);
        assertThat(((NotificationsSentState) entry.extraInfo).blocked).isFalse();
    }

    @Test
    public void testSwitchViews_nullDoesNotCrash() {
        mBridge.enableSwitch(null);
        mBridge.checkSwitch(null);
    }
}
Loading