Remove obsolete FGS notification code
The features that used this code were turned off several years ago. Test: atest SystemUITests Test: run FGS - see notification in shade and app in task manager Fixes: 275709313 Change-Id: Ie21fc03ac243932426e78c36ce2ec34d1a16a173
This commit is contained in:
@@ -131,14 +131,14 @@ import com.android.systemui.util.leak.LeakDetector;
|
||||
import com.android.systemui.util.leak.LeakReporter;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
/**
|
||||
* Class to handle ugly dependencies throughout sysui until we determine the
|
||||
* long-term dependency injection solution.
|
||||
@@ -280,7 +280,6 @@ public class Dependency {
|
||||
@Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
|
||||
@Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
|
||||
@Inject Lazy<TunablePaddingService> mTunablePaddingService;
|
||||
@Inject Lazy<ForegroundServiceController> mForegroundServiceController;
|
||||
@Inject Lazy<UiOffloadThread> mUiOffloadThread;
|
||||
@Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
|
||||
@Inject Lazy<LightBarController> mLightBarController;
|
||||
@@ -458,8 +457,6 @@ public class Dependency {
|
||||
|
||||
mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
|
||||
|
||||
mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
|
||||
|
||||
mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
|
||||
|
||||
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
import android.app.AppOpsManager;
|
||||
import android.os.Handler;
|
||||
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 com.android.systemui.appops.AppOpsController;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.util.Assert;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Tracks state of foreground services and notifications related to foreground services per user.
|
||||
*/
|
||||
@SysUISingleton
|
||||
public class ForegroundServiceController {
|
||||
public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
|
||||
|
||||
private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
|
||||
private final Object mMutex = new Object();
|
||||
private final Handler mMainHandler;
|
||||
|
||||
@Inject
|
||||
public ForegroundServiceController(
|
||||
AppOpsController appOpsController,
|
||||
@Main Handler mainHandler) {
|
||||
mMainHandler = mainHandler;
|
||||
appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
|
||||
mMainHandler.post(() -> {
|
||||
onAppOpChanged(code, uid, packageName, active);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this user has services missing notifications and therefore needs a
|
||||
* disclosure notification for running a foreground service.
|
||||
*/
|
||||
public boolean isDisclosureNeededForUser(int userId) {
|
||||
synchronized (mMutex) {
|
||||
final ForegroundServicesUserState services = mUserServices.get(userId);
|
||||
if (services == null) return false;
|
||||
return services.isDisclosureNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this user/pkg has a missing or custom layout notification and therefore needs
|
||||
* a disclosure notification showing the user which appsOps the app is using.
|
||||
*/
|
||||
public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
|
||||
synchronized (mMutex) {
|
||||
final ForegroundServicesUserState services = mUserServices.get(userId);
|
||||
if (services == null) return false;
|
||||
return services.getStandardLayoutKeys(pkg) == null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets active app ops for this user and package
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records active app ops and updates the app op for the pending or visible notifications
|
||||
* with the given parameters.
|
||||
* App Ops are stored in FSC in addition to NotificationEntry in case they change before we
|
||||
* have a notification to tag.
|
||||
* @param appOpCode code for appOp to add/remove
|
||||
* @param uid of user the notification is sent to
|
||||
* @param packageName package that created the notification
|
||||
* @param active whether the appOpCode is active or not
|
||||
*/
|
||||
void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
|
||||
Assert.isMainThread();
|
||||
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
// Record active app ops
|
||||
synchronized (mMutex) {
|
||||
ForegroundServicesUserState userServices = mUserServices.get(userId);
|
||||
if (userServices == null) {
|
||||
userServices = new ForegroundServicesUserState();
|
||||
mUserServices.put(userId, userServices);
|
||||
}
|
||||
if (active) {
|
||||
userServices.addOp(packageName, appOpCode);
|
||||
} else {
|
||||
userServices.removeOp(packageName, appOpCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 {@code sbn} is the system-provided disclosure notification containing the
|
||||
* list of running foreground services.
|
||||
*/
|
||||
public boolean isDisclosureNotification(StatusBarNotification sbn) {
|
||||
return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
|
||||
&& sbn.getTag() == null
|
||||
&& sbn.getPackageName().equals("android");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if sbn is one of the window manager "drawing over other apps" notifications
|
||||
*/
|
||||
public boolean isSystemAlertNotification(StatusBarNotification sbn) {
|
||||
return sbn.getPackageName().equals("android")
|
||||
&& sbn.getTag() != null
|
||||
&& sbn.getTag().contains("AlertWindowNotification");
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
|
||||
* to perform the update.
|
||||
*/
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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.systemui;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Updates foreground service notification state in response to notification data events. */
|
||||
@SysUISingleton
|
||||
public class ForegroundServiceNotificationListener {
|
||||
|
||||
private static final String TAG = "FgServiceController";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final ForegroundServiceController mForegroundServiceController;
|
||||
private final NotifPipeline mNotifPipeline;
|
||||
|
||||
@Inject
|
||||
public ForegroundServiceNotificationListener(Context context,
|
||||
ForegroundServiceController foregroundServiceController,
|
||||
NotifPipeline notifPipeline) {
|
||||
mContext = context;
|
||||
mForegroundServiceController = foregroundServiceController;
|
||||
mNotifPipeline = notifPipeline;
|
||||
}
|
||||
|
||||
/** Initializes this listener by connecting it to the notification pipeline. */
|
||||
public void init() {
|
||||
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
|
||||
@Override
|
||||
public void onEntryAdded(NotificationEntry entry) {
|
||||
addNotification(entry, entry.getImportance());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryUpdated(NotificationEntry entry) {
|
||||
updateNotification(entry, entry.getImportance());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryRemoved(NotificationEntry entry, int reason) {
|
||||
removeNotification(entry.getSbn());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entry notification that was just posted
|
||||
*/
|
||||
private void addNotification(NotificationEntry entry, int importance) {
|
||||
updateNotification(entry, importance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sbn notification that was just removed
|
||||
*/
|
||||
private void removeNotification(StatusBarNotification sbn) {
|
||||
mForegroundServiceController.updateUserState(
|
||||
sbn.getUserId(),
|
||||
new ForegroundServiceController.UserStateUpdateCallback() {
|
||||
@Override
|
||||
public boolean updateUserState(ForegroundServicesUserState userState) {
|
||||
if (mForegroundServiceController.isDisclosureNotification(sbn)) {
|
||||
// if you remove the dungeon entirely, we take that to mean there are
|
||||
// no running services
|
||||
userState.setRunningServices(null, 0);
|
||||
return true;
|
||||
} else {
|
||||
// this is safe to call on any notification, not just
|
||||
// FLAG_FOREGROUND_SERVICE
|
||||
return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userStateNotFound(int userId) {
|
||||
if (DBG) {
|
||||
Log.w(TAG, String.format(
|
||||
"user %d with no known notifications got removeNotification "
|
||||
+ "for %s",
|
||||
sbn.getUserId(), sbn));
|
||||
}
|
||||
}
|
||||
},
|
||||
false /* don't create */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entry notification that was just changed in some way
|
||||
*/
|
||||
private void updateNotification(NotificationEntry entry, int newImportance) {
|
||||
final StatusBarNotification sbn = entry.getSbn();
|
||||
mForegroundServiceController.updateUserState(
|
||||
sbn.getUserId(),
|
||||
userState -> {
|
||||
if (mForegroundServiceController.isDisclosureNotification(sbn)) {
|
||||
final Bundle extras = sbn.getNotification().extras;
|
||||
if (extras != null) {
|
||||
final String[] svcs = extras.getStringArray(
|
||||
Notification.EXTRA_FOREGROUND_APPS);
|
||||
userState.setRunningServices(svcs, sbn.getNotification().when);
|
||||
}
|
||||
} else {
|
||||
userState.removeNotification(sbn.getPackageName(), sbn.getKey());
|
||||
if (0 != (sbn.getNotification().flags
|
||||
& Notification.FLAG_FOREGROUND_SERVICE)) {
|
||||
if (newImportance > NotificationManager.IMPORTANCE_MIN) {
|
||||
userState.addImportantNotification(sbn.getPackageName(),
|
||||
sbn.getKey());
|
||||
}
|
||||
}
|
||||
final Notification.Builder builder =
|
||||
Notification.Builder.recoverBuilder(
|
||||
mContext, sbn.getNotification());
|
||||
if (builder.usesStandardHeader()) {
|
||||
userState.addStandardLayoutNotification(
|
||||
sbn.getPackageName(), sbn.getKey());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
true /* create if not found */);
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.statusbar.notification.collection.coordinator;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_MIN;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.appops.AppOpsController;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Handles ForegroundService and AppOp interactions with notifications.
|
||||
* Tags notifications with appOps
|
||||
* Lifetime extends notifications associated with an ongoing ForegroundService.
|
||||
* Filters out notifications that represent foreground services that are no longer running
|
||||
* Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
|
||||
* section ordering priority.
|
||||
*
|
||||
* Previously this logic lived in
|
||||
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
|
||||
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
|
||||
* frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
|
||||
*/
|
||||
@CoordinatorScope
|
||||
public class AppOpsCoordinator implements Coordinator {
|
||||
private static final String TAG = "AppOpsCoordinator";
|
||||
|
||||
private final ForegroundServiceController mForegroundServiceController;
|
||||
private final AppOpsController mAppOpsController;
|
||||
private final DelayableExecutor mMainExecutor;
|
||||
|
||||
private NotifPipeline mNotifPipeline;
|
||||
|
||||
@Inject
|
||||
public AppOpsCoordinator(
|
||||
ForegroundServiceController foregroundServiceController,
|
||||
AppOpsController appOpsController,
|
||||
@Main DelayableExecutor mainExecutor) {
|
||||
mForegroundServiceController = foregroundServiceController;
|
||||
mAppOpsController = appOpsController;
|
||||
mMainExecutor = mainExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(NotifPipeline pipeline) {
|
||||
mNotifPipeline = pipeline;
|
||||
|
||||
// filter out foreground service notifications that aren't necessary anymore
|
||||
mNotifPipeline.addPreGroupFilter(mNotifFilter);
|
||||
|
||||
}
|
||||
|
||||
public NotifSectioner getSectioner() {
|
||||
return mNotifSectioner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out notifications that represent foreground services that are no longer running or
|
||||
* that already have an app notification with the appOps tagged to
|
||||
*/
|
||||
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
|
||||
@Override
|
||||
public boolean shouldFilterOut(NotificationEntry entry, long now) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
// Filters out system-posted disclosure notifications when unneeded
|
||||
if (mForegroundServiceController.isDisclosureNotification(sbn)
|
||||
&& !mForegroundServiceController.isDisclosureNeededForUser(
|
||||
sbn.getUser().getIdentifier())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Puts colorized foreground service and call notifications into its own section.
|
||||
*/
|
||||
private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
|
||||
NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
|
||||
@Override
|
||||
public boolean isInSection(ListEntry entry) {
|
||||
NotificationEntry notificationEntry = entry.getRepresentativeEntry();
|
||||
if (notificationEntry != null) {
|
||||
return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isColorizedForegroundService(NotificationEntry entry) {
|
||||
Notification notification = entry.getSbn().getNotification();
|
||||
return notification.isForegroundService()
|
||||
&& notification.isColorized()
|
||||
&& entry.getImportance() > IMPORTANCE_MIN;
|
||||
}
|
||||
|
||||
private boolean isCall(NotificationEntry entry) {
|
||||
Notification notification = entry.getSbn().getNotification();
|
||||
return entry.getImportance() > IMPORTANCE_MIN
|
||||
&& notification.isStyle(Notification.CallStyle.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.statusbar.notification.collection.coordinator;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_MIN;
|
||||
|
||||
import android.app.Notification;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Handles sectioning for foreground service notifications.
|
||||
* Puts non-min colorized foreground service notifications into the FGS section. See
|
||||
* {@link NotifCoordinators} for section ordering priority.
|
||||
*/
|
||||
@CoordinatorScope
|
||||
public class ColorizedFgsCoordinator implements Coordinator {
|
||||
private static final String TAG = "ColorizedCoordinator";
|
||||
|
||||
@Inject
|
||||
public ColorizedFgsCoordinator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(NotifPipeline pipeline) {
|
||||
}
|
||||
|
||||
public NotifSectioner getSectioner() {
|
||||
return mNotifSectioner;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts colorized foreground service and call notifications into its own section.
|
||||
*/
|
||||
private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
|
||||
NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
|
||||
@Override
|
||||
public boolean isInSection(ListEntry entry) {
|
||||
NotificationEntry notificationEntry = entry.getRepresentativeEntry();
|
||||
if (notificationEntry != null) {
|
||||
return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isColorizedForegroundService(NotificationEntry entry) {
|
||||
Notification notification = entry.getSbn().getNotification();
|
||||
return notification.isForegroundService()
|
||||
&& notification.isColorized()
|
||||
&& entry.getImportance() > IMPORTANCE_MIN;
|
||||
}
|
||||
|
||||
private boolean isCall(NotificationEntry entry) {
|
||||
Notification notification = entry.getSbn().getNotification();
|
||||
return entry.getImportance() > IMPORTANCE_MIN
|
||||
&& notification.isStyle(Notification.CallStyle.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -33,34 +33,34 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
|
||||
|
||||
@CoordinatorScope
|
||||
class NotifCoordinatorsImpl @Inject constructor(
|
||||
sectionStyleProvider: SectionStyleProvider,
|
||||
featureFlags: FeatureFlags,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
appOpsCoordinator: AppOpsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
groupWhenCoordinator: GroupWhenCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator,
|
||||
dreamCoordinator: DreamCoordinator,
|
||||
sectionStyleProvider: SectionStyleProvider,
|
||||
featureFlags: FeatureFlags,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
colorizedFgsCoordinator: ColorizedFgsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
groupWhenCoordinator: GroupWhenCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator,
|
||||
dreamCoordinator: DreamCoordinator,
|
||||
) : NotifCoordinators {
|
||||
|
||||
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
|
||||
@@ -79,7 +79,7 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
mCoordinators.add(hideNotifsForOtherUsersCoordinator)
|
||||
mCoordinators.add(keyguardCoordinator)
|
||||
mCoordinators.add(rankingCoordinator)
|
||||
mCoordinators.add(appOpsCoordinator)
|
||||
mCoordinators.add(colorizedFgsCoordinator)
|
||||
mCoordinators.add(deviceProvisionedCoordinator)
|
||||
mCoordinators.add(bubbleCoordinator)
|
||||
mCoordinators.add(debugModeCoordinator)
|
||||
@@ -106,7 +106,7 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
|
||||
// Manually add Ordered Sections
|
||||
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
|
||||
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
|
||||
mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
|
||||
mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
|
||||
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
|
||||
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.statusbar.notification.init
|
||||
|
||||
import android.service.notification.StatusBarNotification
|
||||
import com.android.systemui.ForegroundServiceNotificationListener
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
|
||||
@@ -70,7 +69,6 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
|
||||
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
|
||||
private val bubblesOptional: Optional<Bubbles>,
|
||||
private val fgsNotifListener: ForegroundServiceNotificationListener,
|
||||
private val featureFlags: FeatureFlags
|
||||
) : NotificationsController {
|
||||
|
||||
@@ -105,7 +103,6 @@ class NotificationsControllerImpl @Inject constructor(
|
||||
notificationsMediaManager.setUpWithPresenter(presenter)
|
||||
notificationLogger.setUpWithContainer(listContainer)
|
||||
peopleSpaceWidgetManager.attach(notificationListener)
|
||||
fgsNotifListener.init()
|
||||
}
|
||||
|
||||
// TODO: Convert all functions below this line into listeners instead of public methods
|
||||
|
||||
@@ -1,487 +0,0 @@
|
||||
/*
|
||||
* 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 static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.TestCase.fail;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.messages.nano.SystemMessageProto;
|
||||
import com.android.systemui.appops.AppOpsController;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ForegroundServiceControllerTest extends SysuiTestCase {
|
||||
private ForegroundServiceController mFsc;
|
||||
private ForegroundServiceNotificationListener mListener;
|
||||
private NotifCollectionListener mCollectionListener;
|
||||
@Mock private AppOpsController mAppOpsController;
|
||||
@Mock private Handler mMainHandler;
|
||||
@Mock private NotifPipeline mNotifPipeline;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// allow the TestLooper to be asserted as the main thread these tests
|
||||
allowTestableLooperAsMainThread();
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
|
||||
mListener = new ForegroundServiceNotificationListener(
|
||||
mContext, mFsc, mNotifPipeline);
|
||||
mListener.init();
|
||||
ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
|
||||
ArgumentCaptor.forClass(NotifCollectionListener.class);
|
||||
verify(mNotifPipeline).addCollectionListener(
|
||||
entryListenerCaptor.capture());
|
||||
mCollectionListener = entryListenerCaptor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppOpsChangedCalledFromBgThread() {
|
||||
try {
|
||||
// WHEN onAppOpChanged is called from a different thread than the MainLooper
|
||||
disallowTestableLooperAsMainThread();
|
||||
NotificationEntry entry = createFgEntry();
|
||||
mFsc.onAppOpChanged(
|
||||
AppOpsManager.OP_CAMERA,
|
||||
entry.getSbn().getUid(),
|
||||
entry.getSbn().getPackageName(),
|
||||
true);
|
||||
|
||||
// This test is run on the TestableLooper, which is not the MainLooper, so
|
||||
// we expect an exception to be thrown
|
||||
fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
|
||||
} catch (IllegalStateException e) {
|
||||
// THEN expect an exception
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppOpsCRUD() {
|
||||
// no crash on remove that doesn't exist
|
||||
mFsc.onAppOpChanged(9, 1000, "pkg1", false);
|
||||
assertNull(mFsc.getAppOps(0, "pkg1"));
|
||||
|
||||
// multiuser & multipackage
|
||||
mFsc.onAppOpChanged(8, 50, "pkg1", true);
|
||||
mFsc.onAppOpChanged(1, 60, "pkg3", true);
|
||||
mFsc.onAppOpChanged(7, 500000, "pkg2", true);
|
||||
|
||||
assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
|
||||
|
||||
assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
|
||||
assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
|
||||
|
||||
assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
|
||||
assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
|
||||
|
||||
// multiple ops for the same package
|
||||
mFsc.onAppOpChanged(9, 50, "pkg1", true);
|
||||
mFsc.onAppOpChanged(5, 50, "pkg1", true);
|
||||
|
||||
assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
|
||||
|
||||
assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
|
||||
assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
|
||||
|
||||
// remove one of the multiples
|
||||
mFsc.onAppOpChanged(9, 50, "pkg1", false);
|
||||
assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
|
||||
assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
|
||||
|
||||
// remove last op
|
||||
mFsc.onAppOpChanged(1, 60, "pkg3", false);
|
||||
assertNull(mFsc.getAppOps(0, "pkg3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisclosurePredicate() {
|
||||
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
|
||||
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
|
||||
StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
|
||||
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
|
||||
null, Notification.FLAG_NO_CLEAR);
|
||||
|
||||
assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
|
||||
assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
|
||||
final String PKG1 = "com.example.app100";
|
||||
|
||||
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
|
||||
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
|
||||
StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
|
||||
|
||||
// first add a normal notification
|
||||
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
// nothing required yet
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
// now the app starts a fg service
|
||||
entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
// add the fg notification
|
||||
entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
|
||||
// remove the boring notification
|
||||
entryRemoved(sbn_user1_app1);
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
|
||||
entryRemoved(sbn_user1_app1_fg);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleAddRemove() {
|
||||
final String PKG1 = "com.example.app1";
|
||||
final String PKG2 = "com.example.app2";
|
||||
|
||||
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
|
||||
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
|
||||
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
// no services are "running"
|
||||
entryAdded(makeMockDisclosure(USERID_ONE, null),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
// switch to different package
|
||||
entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
|
||||
|
||||
entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisclosureBasic() {
|
||||
final String PKG1 = "com.example.app0";
|
||||
|
||||
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
|
||||
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
|
||||
StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
|
||||
|
||||
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
|
||||
entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
// let's take out the other notification and see what happens.
|
||||
|
||||
entryRemoved(sbn_user1_app1);
|
||||
assertFalse(
|
||||
mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
// let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
|
||||
StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
|
||||
sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
|
||||
entryUpdated(sbn_user1_app1_fg_sneaky,
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
// ok, ok, we'll put it back
|
||||
sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
|
||||
entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
entryRemoved(sbn_user1_app1_fg_sneaky);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
|
||||
// now let's test an upgrade
|
||||
entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
|
||||
entryUpdated(sbn_user1_app1,
|
||||
NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
|
||||
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
|
||||
// remove it, make sure we're out of compliance again
|
||||
entryRemoved(sbn_user1_app1); // was fg, should return true
|
||||
entryRemoved(sbn_user1_app1);
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
|
||||
// importance upgrade
|
||||
entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
|
||||
assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
|
||||
entryUpdated(sbn_user1_app1_fg,
|
||||
NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
|
||||
|
||||
// finally, let's turn off the service
|
||||
entryAdded(makeMockDisclosure(USERID_ONE, null),
|
||||
NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
|
||||
assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
|
||||
// no notifications nor app op signals that this package/userId requires system alert
|
||||
// warning
|
||||
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomLayouts_systemAlertWarningRequired() {
|
||||
// GIVEN a notification with a custom layout
|
||||
final String pkg = "com.example.app0";
|
||||
StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
|
||||
false);
|
||||
|
||||
// WHEN the custom layout entry is added
|
||||
entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
|
||||
// THEN a system alert warning is required since there aren't any notifications that can
|
||||
// display the app ops
|
||||
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardLayoutExists_noSystemAlertWarningRequired() {
|
||||
// GIVEN two notifications (one with a custom layout, the other with a standard layout)
|
||||
final String pkg = "com.example.app0";
|
||||
StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
|
||||
false);
|
||||
StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
|
||||
|
||||
// WHEN the entries are added
|
||||
entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
|
||||
// THEN no system alert warning is required, since there is at least one notification
|
||||
// with a standard layout that can display the app ops on the notification
|
||||
assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardLayoutRemoved_systemAlertWarningRequired() {
|
||||
// GIVEN two notifications (one with a custom layout, the other with a standard layout)
|
||||
final String pkg = "com.example.app0";
|
||||
StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
|
||||
false);
|
||||
StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
|
||||
|
||||
// WHEN the entries are added and then the standard layout notification is removed
|
||||
entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
entryRemoved(standardLayoutNotif);
|
||||
|
||||
// THEN a system alert warning is required since there aren't any notifications that can
|
||||
// display the app ops
|
||||
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
|
||||
// GIVEN a standard layout notification and then an updated version with a customLayout
|
||||
final String pkg = "com.example.app0";
|
||||
StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
|
||||
StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
|
||||
|
||||
// WHEN the entries is added and then updated to a custom layout
|
||||
entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
|
||||
|
||||
// THEN a system alert warning is required since there aren't any notifications that can
|
||||
// display the app ops
|
||||
assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
|
||||
int flags) {
|
||||
final Notification n = mock(Notification.class);
|
||||
n.extras = new Bundle();
|
||||
n.flags = flags;
|
||||
return makeMockSBN(userId, pkg, id, tag, n);
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
|
||||
Notification n) {
|
||||
final StatusBarNotification sbn = mock(StatusBarNotification.class);
|
||||
when(sbn.getNotification()).thenReturn(n);
|
||||
when(sbn.getId()).thenReturn(id);
|
||||
when(sbn.getPackageName()).thenReturn(pkg);
|
||||
when(sbn.getTag()).thenReturn(tag);
|
||||
when(sbn.getUserId()).thenReturn(userid);
|
||||
when(sbn.getUser()).thenReturn(new UserHandle(userid));
|
||||
when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
|
||||
return sbn;
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
|
||||
boolean usesStdLayout) {
|
||||
StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
|
||||
if (usesStdLayout) {
|
||||
sbn.getNotification().contentView = null;
|
||||
sbn.getNotification().headsUpContentView = null;
|
||||
sbn.getNotification().bigContentView = null;
|
||||
} else {
|
||||
sbn.getNotification().contentView = mock(RemoteViews.class);
|
||||
}
|
||||
return sbn;
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
|
||||
boolean usesStdLayout) {
|
||||
StatusBarNotification sbn =
|
||||
makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
|
||||
if (usesStdLayout) {
|
||||
sbn.getNotification().contentView = null;
|
||||
sbn.getNotification().headsUpContentView = null;
|
||||
sbn.getNotification().bigContentView = null;
|
||||
} else {
|
||||
sbn.getNotification().contentView = mock(RemoteViews.class);
|
||||
}
|
||||
return sbn;
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
|
||||
return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
|
||||
}
|
||||
|
||||
private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
|
||||
final Notification n = mock(Notification.class);
|
||||
n.flags = Notification.FLAG_ONGOING_EVENT;
|
||||
final Bundle extras = new Bundle();
|
||||
if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
|
||||
n.extras = extras;
|
||||
n.when = System.currentTimeMillis() - 10000; // ten seconds ago
|
||||
final StatusBarNotification sbn = makeMockSBN(userid, "android",
|
||||
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
|
||||
null, n);
|
||||
sbn.getNotification().extras = extras;
|
||||
return sbn;
|
||||
}
|
||||
|
||||
private NotificationEntry addFgEntry() {
|
||||
NotificationEntry entry = createFgEntry();
|
||||
mCollectionListener.onEntryAdded(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private NotificationEntry createFgEntry() {
|
||||
return new NotificationEntryBuilder()
|
||||
.setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
|
||||
.setImportance(NotificationManager.IMPORTANCE_DEFAULT)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void entryRemoved(StatusBarNotification notification) {
|
||||
mCollectionListener.onEntryRemoved(
|
||||
new NotificationEntryBuilder()
|
||||
.setSbn(notification)
|
||||
.build(),
|
||||
REASON_APP_CANCEL);
|
||||
}
|
||||
|
||||
private void entryAdded(StatusBarNotification notification, int importance) {
|
||||
NotificationEntry entry = new NotificationEntryBuilder()
|
||||
.setSbn(notification)
|
||||
.setImportance(importance)
|
||||
.build();
|
||||
mCollectionListener.onEntryAdded(entry);
|
||||
}
|
||||
|
||||
private void entryUpdated(StatusBarNotification notification, int importance) {
|
||||
NotificationEntry entry = new NotificationEntryBuilder()
|
||||
.setSbn(notification)
|
||||
.setImportance(importance)
|
||||
.build();
|
||||
mCollectionListener.onEntryUpdated(entry);
|
||||
}
|
||||
|
||||
@UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
|
||||
@UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
|
||||
private static final String TEST_PACKAGE_NAME = "test";
|
||||
}
|
||||
@@ -24,97 +24,53 @@ import static android.app.NotificationManager.IMPORTANCE_MIN;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Person;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.ForegroundServiceController;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.appops.AppOpsController;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
|
||||
import com.android.systemui.util.concurrency.FakeExecutor;
|
||||
import com.android.systemui.util.time.FakeSystemClock;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class AppOpsCoordinatorTest extends SysuiTestCase {
|
||||
private static final String TEST_PKG = "test_pkg";
|
||||
private static final int NOTIF_USER_ID = 0;
|
||||
public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
|
||||
|
||||
@Mock private ForegroundServiceController mForegroundServiceController;
|
||||
@Mock private AppOpsController mAppOpsController;
|
||||
private static final int NOTIF_USER_ID = 0;
|
||||
@Mock private NotifPipeline mNotifPipeline;
|
||||
|
||||
private NotificationEntryBuilder mEntryBuilder;
|
||||
private AppOpsCoordinator mAppOpsCoordinator;
|
||||
private NotifFilter mForegroundFilter;
|
||||
private ColorizedFgsCoordinator mColorizedFgsCoordinator;
|
||||
private NotifSectioner mFgsSection;
|
||||
|
||||
private FakeSystemClock mClock = new FakeSystemClock();
|
||||
private FakeExecutor mExecutor = new FakeExecutor(mClock);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
allowTestableLooperAsMainThread();
|
||||
|
||||
mAppOpsCoordinator =
|
||||
new AppOpsCoordinator(
|
||||
mForegroundServiceController,
|
||||
mAppOpsController,
|
||||
mExecutor);
|
||||
mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
|
||||
|
||||
mEntryBuilder = new NotificationEntryBuilder()
|
||||
.setUser(new UserHandle(NOTIF_USER_ID));
|
||||
|
||||
mAppOpsCoordinator.attach(mNotifPipeline);
|
||||
mColorizedFgsCoordinator.attach(mNotifPipeline);
|
||||
|
||||
// capture filter
|
||||
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
|
||||
verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
|
||||
mForegroundFilter = filterCaptor.getValue();
|
||||
|
||||
mFgsSection = mAppOpsCoordinator.getSectioner();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterTest_disclosureUnnecessary() {
|
||||
NotificationEntry entry = mEntryBuilder.build();
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
// GIVEN the notification is a disclosure notification
|
||||
when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
|
||||
|
||||
// GIVEN the disclosure isn't needed for this user
|
||||
when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
|
||||
.thenReturn(false);
|
||||
|
||||
// THEN filter out the notification
|
||||
assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
|
||||
mFgsSection = mColorizedFgsCoordinator.getSectioner();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -32,7 +32,6 @@ import android.testing.TestableLooper.RunWithLooper;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.testing.FakeMetricsLogger;
|
||||
import com.android.systemui.ForegroundServiceNotificationListener;
|
||||
import com.android.systemui.InitController;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
@@ -94,7 +93,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
|
||||
mDependency.injectTestDependency(ShadeController.class, mShadeController);
|
||||
mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
|
||||
mDependency.injectMockDependency(NotificationShadeWindowController.class);
|
||||
mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
|
||||
|
||||
NotificationShadeWindowView notificationShadeWindowView =
|
||||
mock(NotificationShadeWindowView.class);
|
||||
|
||||
Reference in New Issue
Block a user