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

Commit 190a211a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Display recent apps in notification settings"

parents 06af7c8d 02af3659
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -6965,6 +6965,9 @@
    <!-- Configure Notifications Settings title. [CHAR LIMIT=30] -->
    <string name="configure_notification_settings">Notifications</string>
    <!-- notification header - apps that have recently sent notifications -->
    <string name="recent_notifications">Recently sent</string>
    <!-- Configure Notifications: Advanced section header [CHAR LIMIT=30] -->
    <string name="advanced_section_header">Advanced</string>
+22 −2
Original line number Diff line number Diff line
@@ -19,8 +19,28 @@
                  android:key="configure_notification_settings">

    <PreferenceCategory
        android:key="dashboard_tile_placeholder"
        android:order="1"/>
        android:key="recent_notifications_category"
        android:title="@string/recent_notifications"
        android:order="-200">
        <!-- Placeholder for a list of recent apps -->

        <!-- See all apps button -->
        <Preference
            android:title="@string/notifications_title"
            android:key="all_notifications"
            android:order="20">
            <intent
                android:action="android.intent.action.MAIN"
                android:targetPackage="com.android.settings"
                android:targetClass="com.android.settings.Settings$NotificationAppListActivity">
            </intent>
        </Preference>
    </PreferenceCategory>

    <!-- Empty category to draw divider -->
    <PreferenceCategory
        android:key="all_notifications_divider"
        android:order="-190"/>

    <!-- When device is locked -->
    <com.android.settings.notification.RestrictedDropDownPreference
+14 −3
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.settings.notification;

import android.app.Activity;
import android.app.Application;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -77,11 +79,18 @@ public class ConfigureNotificationSettings extends DashboardFragment {

    @Override
    protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
        return buildPreferenceControllers(context, getLifecycle());
        final Activity activity = getActivity();
        final Application app;
        if (activity != null) {
            app = activity.getApplication();
        } else {
            app = null;
        }
        return buildPreferenceControllers(context, getLifecycle(), app, this);
    }

    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Lifecycle lifecycle) {
            Lifecycle lifecycle, Application app, Fragment host) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        final BadgingNotificationPreferenceController badgeController =
                new BadgingNotificationPreferenceController(context);
@@ -96,6 +105,8 @@ public class ConfigureNotificationSettings extends DashboardFragment {
            lifecycle.addObserver(pulseController);
            lifecycle.addObserver(lockScreenNotificationController);
        }
        controllers.add(new RecentNotifyingAppsPreferenceController(
                context, new NotificationBackend(), app, host));
        controllers.add(new SwipeToNotificationPreferenceController(context, lifecycle,
                KEY_SWIPE_DOWN));
        controllers.add(badgeController);
@@ -167,7 +178,7 @@ public class ConfigureNotificationSettings extends DashboardFragment {
                @Override
                public List<AbstractPreferenceController> getPreferenceControllers(
                        Context context) {
                    return buildPreferenceControllers(context, null);
                    return buildPreferenceControllers(context, null, null, null);
                }

                @Override
+13 −1
Original line number Diff line number Diff line
@@ -27,12 +27,16 @@ import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Drawable;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.NotifyingApp;
import android.util.IconDrawableFactory;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;

import java.util.ArrayList;
import java.util.List;

public class NotificationBackend {
    private static final String TAG = "NotificationBackend";

@@ -185,7 +189,6 @@ public class NotificationBackend {
        }
    }


    public int getDeletedChannelCount(String pkg, int uid) {
        try {
            return sINM.getDeletedChannelCount(pkg, uid);
@@ -204,6 +207,15 @@ public class NotificationBackend {
        }
    }

    public List<NotifyingApp> getRecentApps() {
        try {
            return sINM.getRecentNotifyingAppsForUser(UserHandle.myUserId()).getList();
        } catch (Exception e) {
            Log.w(TAG, "Error calling NoMan", e);
            return new ArrayList<>();
        }
    }

    static class Row {
        public String section;
    }
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.notification;

import android.app.Application;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.service.notification.NotifyingApp;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
import android.util.Log;

import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.InstalledAppCounter;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.AppPreference;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.wrapper.PackageManagerWrapper;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This controller displays a list of recently used apps and a "See all" button. If there is
 * no recently used app, "See all" will be displayed as "Notifications".
 */
public class RecentNotifyingAppsPreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin {

    private static final String TAG = "RecentNotisCtrl";
    private static final String KEY_PREF_CATEGORY = "recent_notifications_category";
    @VisibleForTesting
    static final String KEY_DIVIDER = "all_notifications_divider";
    @VisibleForTesting
    static final String KEY_SEE_ALL = "all_notifications";
    private static final int SHOW_RECENT_APP_COUNT = 5;
    private static final Set<String> SKIP_SYSTEM_PACKAGES = new ArraySet<>();

    private final Fragment mHost;
    private final PackageManager mPm;
    private final NotificationBackend mNotificationBackend;
    private final int mUserId;
    private final IconDrawableFactory mIconDrawableFactory;

    private List<NotifyingApp> mApps;
    private final ApplicationsState mApplicationsState;

    private PreferenceCategory mCategory;
    private Preference mSeeAllPref;
    private Preference mDivider;
    private boolean mHasRecentApps;

    static {
        SKIP_SYSTEM_PACKAGES.addAll(Arrays.asList(
                "android",
                "com.android.phone",
                "com.android.settings",
                "com.android.systemui",
                "com.android.providers.calendar",
                "com.android.providers.media"
        ));
    }

    public RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend,
            Application app, Fragment host) {
        this(context, backend, app == null ? null : ApplicationsState.getInstance(app), host);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    RecentNotifyingAppsPreferenceController(Context context, NotificationBackend backend,
            ApplicationsState appState, Fragment host) {
        super(context);
        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
        mUserId = UserHandle.myUserId();
        mPm = context.getPackageManager();
        mHost = host;
        mApplicationsState = appState;
        mNotificationBackend = backend;
    }

    @Override
    public boolean isAvailable() {
        return true;
    }

    @Override
    public String getPreferenceKey() {
        return KEY_PREF_CATEGORY;
    }

    @Override
    public void updateNonIndexableKeys(List<String> keys) {
        PreferenceControllerMixin.super.updateNonIndexableKeys(keys);
        // Don't index category name into search. It's not actionable.
        keys.add(KEY_PREF_CATEGORY);
        keys.add(KEY_DIVIDER);
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        mCategory = (PreferenceCategory) screen.findPreference(getPreferenceKey());
        mSeeAllPref = screen.findPreference(KEY_SEE_ALL);
        mDivider = screen.findPreference(KEY_DIVIDER);
        super.displayPreference(screen);
        refreshUi(mCategory.getContext());
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        refreshUi(mCategory.getContext());
        // Show total number of installed apps as See all's summary.
        new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,
                new PackageManagerWrapper(mContext.getPackageManager())) {
            @Override
            protected void onCountComplete(int num) {
                if (mHasRecentApps) {
                    mSeeAllPref.setTitle(mContext.getString(R.string.see_all_apps_title, num));
                } else {
                    mSeeAllPref.setSummary(mContext.getString(R.string.apps_summary, num));
                }
            }
        }.execute();

    }

    @VisibleForTesting
    void refreshUi(Context prefContext) {
        reloadData();
        final List<NotifyingApp> recentApps = getDisplayableRecentAppList();
        if (recentApps != null && !recentApps.isEmpty()) {
            mHasRecentApps = true;
            displayRecentApps(prefContext, recentApps);
        } else {
            mHasRecentApps = false;
            displayOnlyAllAppsLink();
        }
    }

    @VisibleForTesting
    void reloadData() {
        mApps = mNotificationBackend.getRecentApps();
    }

    private void displayOnlyAllAppsLink() {
        mCategory.setTitle(null);
        mDivider.setVisible(false);
        mSeeAllPref.setTitle(R.string.notifications_title);
        mSeeAllPref.setIcon(null);
        int prefCount = mCategory.getPreferenceCount();
        for (int i = prefCount - 1; i >= 0; i--) {
            final Preference pref = mCategory.getPreference(i);
            if (!TextUtils.equals(pref.getKey(), KEY_SEE_ALL)) {
                mCategory.removePreference(pref);
            }
        }
    }

    private void displayRecentApps(Context prefContext, List<NotifyingApp> recentApps) {
        mCategory.setTitle(R.string.recent_notifications);
        mDivider.setVisible(true);
        mSeeAllPref.setSummary(null);
        mSeeAllPref.setIcon(R.drawable.ic_chevron_right_24dp);

        // Rebind prefs/avoid adding new prefs if possible. Adding/removing prefs causes jank.
        // Build a cached preference pool
        final Map<String, Preference> appPreferences = new ArrayMap<>();
        int prefCount = mCategory.getPreferenceCount();
        for (int i = 0; i < prefCount; i++) {
            final Preference pref = mCategory.getPreference(i);
            final String key = pref.getKey();
            if (!TextUtils.equals(key, KEY_SEE_ALL)) {
                appPreferences.put(key, pref);
            }
        }
        final int recentAppsCount = recentApps.size();
        for (int i = 0; i < recentAppsCount; i++) {
            final NotifyingApp app = recentApps.get(i);
            // Bind recent apps to existing prefs if possible, or create a new pref.
            final String pkgName = app.getPackage();
            final ApplicationsState.AppEntry appEntry =
                    mApplicationsState.getEntry(app.getPackage(), mUserId);
            if (appEntry == null) {
                continue;
            }

            boolean rebindPref = true;
            Preference pref = appPreferences.remove(pkgName);
            if (pref == null) {
                pref = new AppPreference(prefContext);
                rebindPref = false;
            }
            pref.setKey(pkgName);
            pref.setTitle(appEntry.label);
            pref.setIcon(mIconDrawableFactory.getBadgedIcon(appEntry.info));
            pref.setSummary(Utils.formatRelativeTime(mContext,
                    System.currentTimeMillis() - app.getLastNotified(), false));
            pref.setOrder(i);
            pref.setOnPreferenceClickListener(preference -> {
                AppInfoBase.startAppInfoFragment(AppNotificationSettings.class,
                        R.string.notifications_title, pkgName, appEntry.info.uid, mHost,
                        1001 /*RequestCode */,
                        MetricsProto.MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS);
                    return true;
            });
            if (!rebindPref) {
                mCategory.addPreference(pref);
            }
        }
        // Remove unused prefs from pref cache pool
        for (Preference unusedPrefs : appPreferences.values()) {
            mCategory.removePreference(unusedPrefs);
        }
    }

    private List<NotifyingApp> getDisplayableRecentAppList() {
        Collections.sort(mApps);
        List<NotifyingApp> displayableApps = new ArrayList<>(SHOW_RECENT_APP_COUNT);
        int count = 0;
        for (NotifyingApp app : mApps) {
            final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry(
                    app.getPackage(), mUserId);
            if (appEntry == null) {
                continue;
            }
            if (!shouldIncludePkgInRecents(app.getPackage())) {
                continue;
            }
            displayableApps.add(app);
            count++;
            if (count >= SHOW_RECENT_APP_COUNT) {
                break;
            }
        }
        return displayableApps;
    }


    /**
     * Whether or not the app should be included in recent list.
     */
    private boolean shouldIncludePkgInRecents(String pkgName) {
         if (SKIP_SYSTEM_PACKAGES.contains(pkgName)) {
            Log.d(TAG, "System package, skipping " + pkgName);
            return false;
        }
        final Intent launchIntent = new Intent().addCategory(Intent.CATEGORY_LAUNCHER)
                .setPackage(pkgName);

        if (mPm.resolveActivity(launchIntent, 0) == null) {
            // Not visible on launcher -> likely not a user visible app, skip if non-instant.
            final ApplicationsState.AppEntry appEntry =
                    mApplicationsState.getEntry(pkgName, mUserId);
            if (!AppUtils.isInstant(appEntry.info)) {
                Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName);
                return false;
            }
        }
        return true;
    }
}
Loading