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

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

Merge changes from topic "foreground-service-controller-2"

* changes:
  Factors out notification listening from ForegroundServiceController.
  Collapses ForegroundServiceControllerImpl into interface.
parents e4e854c0 eb4e2e11
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -278,6 +278,7 @@ public class Dependency extends SystemUI {
    Lazy<NotificationAlertingManager> mNotificationAlertingManager;
    @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
    @Inject Lazy<AutoHideController> mAutoHideController;
    @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
    @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
    @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
    @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
@@ -450,6 +451,8 @@ public class Dependency extends SystemUI {
        mProviders.put(BubbleController.class, mBubbleController::get);
        mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
        mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
        mProviders.put(ForegroundServiceNotificationListener.class,
                mForegroundServiceNotificationListener::get);

        // TODO(b/118592525): to support multi-display , we start to add something which is
        //                    per-display, while others may be global. I think it's time to add
+0 −6
Original line number Diff line number Diff line
@@ -216,12 +216,6 @@ public abstract class DependencyBinder {
    public abstract VolumeDialogController provideVolumeDialogController(
            VolumeDialogControllerImpl controllerImpl);

    /**
     */
    @Binds
    public abstract ForegroundServiceController provideForegroundService(
            ForegroundServiceControllerImpl controllerImpl);

    /**
     */
    @Binds
+124 −31
Original line number Diff line number Diff line
@@ -15,65 +15,158 @@
package com.android.systemui;

import android.annotation.Nullable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.SparseArray;

import com.android.internal.messages.nano.SystemMessageProto;

import javax.inject.Inject;
import javax.inject.Singleton;

public interface ForegroundServiceController {
/**
     * @param sbn notification that was just posted
     * @param importance
 * Tracks state of foreground services and notifications related to foreground services per user.
 */
    void addNotification(StatusBarNotification sbn, int importance);
@Singleton
public class ForegroundServiceController {

    private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
    private final Object mMutex = new Object();

    @Inject
    public ForegroundServiceController() {
    }

    /**
     * @param sbn notification that was just changed in some way
     * @param newImportance
     * @return true if this user has services missing notifications and therefore needs a
     * disclosure notification.
     */
    void updateNotification(StatusBarNotification sbn, int newImportance);
    public boolean isDisclosureNeededForUser(int userId) {
        synchronized (mMutex) {
            final ForegroundServicesUserState services = mUserServices.get(userId);
            if (services == null) return false;
            return services.isDisclosureNeeded();
        }
    }

    /**
     * @param sbn notification that was just canceled
     * @return true if this user/pkg has a missing or custom layout notification and therefore needs
     * a disclosure notification for system alert windows.
     */
    boolean removeNotification(StatusBarNotification sbn);
    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
        synchronized (mMutex) {
            final ForegroundServicesUserState services = mUserServices.get(userId);
            if (services == null) return false;
            return services.getStandardLayoutKey(pkg) == null;
        }
    }

    /**
     * @param userId
     * @return true if this user has services missing notifications and therefore needs a
     * disclosure notification.
     * Returns the key of the foreground service from this package using the standard template,
     * if one exists.
     */
    boolean isDungeonNeededForUser(int userId);
    @Nullable
    public String getStandardLayoutKey(int userId, String pkg) {
        synchronized (mMutex) {
            final ForegroundServicesUserState services = mUserServices.get(userId);
            if (services == null) return null;
            return services.getStandardLayoutKey(pkg);
        }
    }

    /**
     * @param sbn
     * @return true if sbn is the system-provided "dungeon" (list of running foreground services).
     * Gets active app ops for this user and package
     */
    boolean isDungeonNotification(StatusBarNotification sbn);
    @Nullable
    public ArraySet<Integer> getAppOps(int userId, String pkg) {
        synchronized (mMutex) {
            final ForegroundServicesUserState services = mUserServices.get(userId);
            if (services == null) {
                return null;
            }
            return services.getFeatures(pkg);
        }
    }

    /**
     * @return true if sbn is one of the window manager "drawing over other apps" notifications
     * Records active app ops. App Ops are stored in FSC in addition to NotificationData in
     * case they change before we have a notification to tag.
     */
    boolean isSystemAlertNotification(StatusBarNotification sbn);
    public void onAppOpChanged(int code, int uid, String packageName, boolean active) {
        int userId = UserHandle.getUserId(uid);
        synchronized (mMutex) {
            ForegroundServicesUserState userServices = mUserServices.get(userId);
            if (userServices == null) {
                userServices = new ForegroundServicesUserState();
                mUserServices.put(userId, userServices);
            }
            if (active) {
                userServices.addOp(packageName, code);
            } else {
                userServices.removeOp(packageName, code);
            }
        }
    }

    /**
     * Returns the key of the foreground service from this package using the standard template,
     * if one exists.
     * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
     * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
     * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
     * If {@code createIfNotFound} is false, no update is performed.
     *
     * @return false if no user state was found and none was created; true otherwise.
     */
    @Nullable String getStandardLayoutKey(int userId, String pkg);
    boolean updateUserState(int userId,
            UserStateUpdateCallback updateCallback,
            boolean createIfNotFound) {
        synchronized (mMutex) {
            ForegroundServicesUserState userState = mUserServices.get(userId);
            if (userState == null) {
                if (createIfNotFound) {
                    userState = new ForegroundServicesUserState();
                    mUserServices.put(userId, userState);
                } else {
                    return false;
                }
            }
            return updateCallback.updateUserState(userState);
        }
    }

    /**
     * @return true if this user/pkg has a missing or custom layout notification and therefore needs
     * a disclosure notification for system alert windows.
     * @return true if {@code sbn} is the system-provided disclosure notification containing the
     * list of running foreground services.
     */
    boolean isSystemAlertWarningNeeded(int userId, String pkg);
    public boolean isDisclosureNotification(StatusBarNotification sbn) {
        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
                && sbn.getTag() == null
                && sbn.getPackageName().equals("android");
    }

    /**
     * Records active app ops. App Ops are stored in FSC in addition to NotificationData in
     * case they change before we have a notification to tag.
     * @return true if sbn is one of the window manager "drawing over other apps" notifications
     */
    void onAppOpChanged(int code, int uid, String packageName, boolean active);
    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
        return sbn.getPackageName().equals("android")
                && sbn.getTag() != null
                && sbn.getTag().contains("AlertWindowNotification");
    }

    /**
     * Gets active app ops for this user and package
     * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
     * to perform the update.
     */
    @Nullable ArraySet<Integer> getAppOps(int userId, String packageName);
    interface UserStateUpdateCallback {
        /**
         * Perform update operations on the provided {@code userState}.
         *
         * @return true if the update succeeded.
         */
        boolean updateUserState(ForegroundServicesUserState userState);

        /** Called if the state was not found and was not created. */
        default void userStateNotFound(int userId) {
        }
    }
}
+0 −309
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.systemui;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.messages.nano.SystemMessageProto;

import java.util.Arrays;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * Foreground service controller, a/k/a Dianne's Dungeon.
 */
@Singleton
public class ForegroundServiceControllerImpl
        implements ForegroundServiceController {

    // shelf life of foreground services before they go bad
    public static final long FG_SERVICE_GRACE_MILLIS = 5000;

    private static final String TAG = "FgServiceController";
    private static final boolean DBG = false;

    private final Context mContext;
    private final SparseArray<UserServices> mUserServices = new SparseArray<>();
    private final Object mMutex = new Object();

    @Inject
    public ForegroundServiceControllerImpl(Context context) {
        mContext = context;
    }

    @Override
    public boolean isDungeonNeededForUser(int userId) {
        synchronized (mMutex) {
            final UserServices services = mUserServices.get(userId);
            if (services == null) return false;
            return services.isDungeonNeeded();
        }
    }

    @Override
    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
        synchronized (mMutex) {
            final UserServices services = mUserServices.get(userId);
            if (services == null) return false;
            return services.getStandardLayoutKey(pkg) == null;
        }
    }

    @Override
    public String getStandardLayoutKey(int userId, String pkg) {
        synchronized (mMutex) {
            final UserServices services = mUserServices.get(userId);
            if (services == null) return null;
            return services.getStandardLayoutKey(pkg);
        }
    }

    @Override
    public ArraySet<Integer> getAppOps(int userId, String pkg) {
        synchronized (mMutex) {
            final UserServices services = mUserServices.get(userId);
            if (services == null) {
                return null;
            }
            return services.getFeatures(pkg);
        }
    }

    @Override
    public void onAppOpChanged(int code, int uid, String packageName, boolean active) {
        int userId = UserHandle.getUserId(uid);
        synchronized (mMutex) {
            UserServices userServices = mUserServices.get(userId);
            if (userServices == null) {
                userServices = new UserServices();
                mUserServices.put(userId, userServices);
            }
            if (active) {
                userServices.addOp(packageName, code);
            } else {
                userServices.removeOp(packageName, code);
            }
        }
    }

    @Override
    public void addNotification(StatusBarNotification sbn, int importance) {
        updateNotification(sbn, importance);
    }

    @Override
    public boolean removeNotification(StatusBarNotification sbn) {
        synchronized (mMutex) {
            final UserServices userServices = mUserServices.get(sbn.getUserId());
            if (userServices == null) {
                if (DBG) {
                    Log.w(TAG, String.format(
                            "user %d with no known notifications got removeNotification for %s",
                            sbn.getUserId(), sbn));
                }
                return false;
            }
            if (isDungeonNotification(sbn)) {
                // if you remove the dungeon entirely, we take that to mean there are
                // no running services
                userServices.setRunningServices(null, 0);
                return true;
            } else {
                // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
                return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
            }
        }
    }

    @Override
    public void updateNotification(StatusBarNotification sbn, int newImportance) {
        synchronized (mMutex) {
            UserServices userServices = mUserServices.get(sbn.getUserId());
            if (userServices == null) {
                userServices = new UserServices();
                mUserServices.put(sbn.getUserId(), userServices);
            }

            if (isDungeonNotification(sbn)) {
                final Bundle extras = sbn.getNotification().extras;
                if (extras != null) {
                    final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
                    userServices.setRunningServices(svcs, sbn.getNotification().when);
                }
            } else {
                userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
                if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) {
                    if (newImportance > NotificationManager.IMPORTANCE_MIN) {
                        userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey());
                    }
                    final Notification.Builder builder = Notification.Builder.recoverBuilder(
                            mContext, sbn.getNotification());
                    if (builder.usesStandardHeader()) {
                        userServices.addStandardLayoutNotification(
                                sbn.getPackageName(), sbn.getKey());
                    }
                }
            }
        }
    }

    @Override
    public boolean isDungeonNotification(StatusBarNotification sbn) {
        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
                && sbn.getTag() == null
                && sbn.getPackageName().equals("android");
    }

    @Override
    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
        return sbn.getPackageName().equals("android")
                && sbn.getTag() != null
                && sbn.getTag().contains("AlertWindowNotification");
    }

    /**
     * Struct to track relevant packages and notifications for a userid's foreground services.
     */
    private static class UserServices {
        private String[] mRunning = null;
        private long mServiceStartTime = 0;
        // package -> sufficiently important posted notification keys
        private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1);
        // package -> standard layout posted notification keys
        private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1);

        // package -> app ops
        private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1);

        public void setRunningServices(String[] pkgs, long serviceStartTime) {
            mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
            mServiceStartTime = serviceStartTime;
        }

        public void addOp(String pkg, int op) {
            if (mAppOps.get(pkg) == null) {
                mAppOps.put(pkg, new ArraySet<>(3));
            }
            mAppOps.get(pkg).add(op);
        }

        public boolean removeOp(String pkg, int op) {
            final boolean found;
            final ArraySet<Integer> keys = mAppOps.get(pkg);
            if (keys == null) {
                found = false;
            } else {
                found = keys.remove(op);
                if (keys.size() == 0) {
                    mAppOps.remove(pkg);
                }
            }
            return found;
        }

        public void addImportantNotification(String pkg, String key) {
            addNotification(mImportantNotifications, pkg, key);
        }

        public boolean removeImportantNotification(String pkg, String key) {
            return removeNotification(mImportantNotifications, pkg, key);
        }

        public void addStandardLayoutNotification(String pkg, String key) {
            addNotification(mStandardLayoutNotifications, pkg, key);
        }

        public boolean removeStandardLayoutNotification(String pkg, String key) {
            return removeNotification(mStandardLayoutNotifications, pkg, key);
        }

        public boolean removeNotification(String pkg, String key) {
            boolean removed = false;
            removed |= removeImportantNotification(pkg, key);
            removed |= removeStandardLayoutNotification(pkg, key);
            return removed;
        }

        public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
                String key) {
            if (map.get(pkg) == null) {
                map.put(pkg, new ArraySet<>());
            }
            map.get(pkg).add(key);
        }

        public boolean removeNotification(ArrayMap<String, ArraySet<String>> map,
                String pkg, String key) {
            final boolean found;
            final ArraySet<String> keys = map.get(pkg);
            if (keys == null) {
                found = false;
            } else {
                found = keys.remove(key);
                if (keys.size() == 0) {
                    map.remove(pkg);
                }
            }
            return found;
        }

        public boolean isDungeonNeeded() {
            if (mRunning != null
                && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {

                for (String pkg : mRunning) {
                    final ArraySet<String> set = mImportantNotifications.get(pkg);
                    if (set == null || set.size() == 0) {
                        return true;
                    }
                }
            }
            return false;
        }

        public ArraySet<Integer> getFeatures(String pkg) {
            return mAppOps.get(pkg);
        }

        public String getStandardLayoutKey(String pkg) {
            final ArraySet<String> set = mStandardLayoutNotifications.get(pkg);
            if (set == null || set.size() == 0) {
                return null;
            }
            return set.valueAt(0);
        }

        @Override
        public String toString() {
            return "UserServices{" +
                    "mRunning=" + Arrays.toString(mRunning) +
                    ", mServiceStartTime=" + mServiceStartTime +
                    ", mImportantNotifications=" + mImportantNotifications +
                    ", mStandardLayoutNotifications=" + mStandardLayoutNotifications +
                    '}';
        }
    }
}
+152 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading