Merge changes from topic 'dungeon' into oc-dev

* changes:
  Hide redundant foreground service notifications.
  Updates to Dianne's Dungeon.
This commit is contained in:
Daniel Sandler
2017-06-05 19:03:26 +00:00
committed by Android (Google) Code Review
10 changed files with 615 additions and 47 deletions

View File

@@ -135,7 +135,7 @@ public class SystemNotificationChannels {
channelsList.add(new NotificationChannel(
FOREGROUND_SERVICE,
context.getString(R.string.notification_channel_foreground_service),
NotificationManager.IMPORTANCE_MIN));
NotificationManager.IMPORTANCE_LOW));
nm.createNotificationChannels(channelsList);
}

View File

@@ -0,0 +1,29 @@
<!--
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.
-->
<!-- "system vitals", as represented by an EKG trace -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.5608645,12.0797103 L17.15,5.15 L15.25,5.15 L11.95,15.95 L9.75,11.5 L7.95,11.55 L7.2,13.3
L6.65,14.6 L3.25,14.6 L3.25,16.6 L7.35,16.6 L8,16.6 L8.25,16 L8.9,14.3 L11.2,18.85 L13.15,18.85 L16.25,8.8
L17.5310733,12.642689 C17.2014325,12.9992627 17,13.4761078 17,14 C17,15.1045695 17.8954305,16 19,16
C20.1045695,16 21,15.1045695 21,14 C21,13.0901368 20.3924276,12.3221796 19.5608645,12.0797103 Z M21,3
C22,3 23,4 23,5 L23,19 C23,20 22,21 21,21 L3,21 C1.9,21 1,20.1 1,19 L1,5 C1,4 2,3 3,3 L21,3 Z" />
</vector>

View File

@@ -3024,4 +3024,5 @@
<java-symbol type="bool" name="config_handleVolumeKeysInWindowManager" />
<java-symbol type="integer" name="config_inCallNotificationVolumeRelative" />
<java-symbol type="drawable" name="stat_sys_vitals" />
</resources>

View File

@@ -264,6 +264,9 @@ public class Dependency extends SystemUI {
mProviders.put(AccessibilityManagerWrapper.class,
() -> new AccessibilityManagerWrapper(mContext));
mProviders.put(ForegroundServiceController.class,
() -> new ForegroundServiceControllerImpl(mContext));
mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
// Put all dependencies above here so the factory can override them if it wants.

View File

@@ -0,0 +1,49 @@
/*
* 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.service.notification.StatusBarNotification;
public interface ForegroundServiceController {
/**
* @param sbn notification that was just posted
* @param importance
*/
void addNotification(StatusBarNotification sbn, int importance);
/**
* @param sbn notification that was just changed in some way
* @param newImportance
*/
void updateNotification(StatusBarNotification sbn, int newImportance);
/**
* @param sbn notification that was just canceled
*/
boolean removeNotification(StatusBarNotification sbn);
/**
* @param userId
* @return true if this user has services missing notifications and therefore needs a
* disclosure notification.
*/
boolean isDungeonNeededForUser(int userId);
/**
* @param sbn
* @return true if sbn is the system-provided "dungeon" (list of running foreground services).
*/
boolean isDungeonNotification(StatusBarNotification sbn);
}

View File

@@ -0,0 +1,156 @@
/*
* 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.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
import java.util.Arrays;
/**
* Foreground service controller, a/k/a Dianne's Dungeon.
*/
public class ForegroundServiceControllerImpl
implements ForegroundServiceController {
private static final String TAG = "FgServiceController";
private static final boolean DBG = false;
private final SparseArray<UserServices> mUserServices = new SparseArray<>();
private final Object mMutex = new Object();
public ForegroundServiceControllerImpl(Context 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 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);
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); // null ok
}
} else {
userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
&& newImportance > NotificationManager.IMPORTANCE_MIN) {
userServices.addNotification(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");
}
/**
* Struct to track relevant packages and notifications for a userid's foreground services.
*/
private static class UserServices {
private String[] mRunning = null;
private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
public void setRunningServices(String[] pkgs) {
mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
}
public void addNotification(String pkg, String key) {
if (mNotifications.get(pkg) == null) {
mNotifications.put(pkg, new ArraySet<String>());
}
mNotifications.get(pkg).add(key);
}
public boolean removeNotification(String pkg, String key) {
final boolean found;
final ArraySet<String> keys = mNotifications.get(pkg);
if (keys == null) {
found = false;
} else {
found = keys.remove(key);
if (keys.size() == 0) {
mNotifications.remove(pkg);
}
}
return found;
}
public boolean isDungeonNeeded() {
if (mRunning != null) {
for (String pkg : mRunning) {
final ArraySet<String> set = mNotifications.get(pkg);
if (set == null || set.size() == 0) {
return true;
}
}
}
return false;
}
}
}

View File

@@ -24,6 +24,8 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
@@ -38,8 +40,11 @@ import android.widget.RemoteViews;
import android.Manifest;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -339,6 +344,7 @@ public class NotificationData {
mEntries.put(entry.notification.getKey(), entry);
}
mGroupManager.onEntryAdded(entry);
updateRankingAndSort(mRankingMap);
}
@@ -466,6 +472,10 @@ public class NotificationData {
Collections.sort(mSortedAndFiltered, mRankingComparator);
}
/**
* @param sbn
* @return true if this notification should NOT be shown right now
*/
public boolean shouldFilterOut(StatusBarNotification sbn) {
if (!(mEnvironment.isDeviceProvisioned() ||
showNotificationEvenIfUnprovisioned(sbn))) {
@@ -487,6 +497,13 @@ public class NotificationData {
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
return true;
}
final ForegroundServiceController fsc = Dependency.get(ForegroundServiceController.class);
if (fsc.isDungeonNotification(sbn) && !fsc.isDungeonNeededForUser(sbn.getUserId())) {
// this is a foreground-service disclosure for a user that does not need to show one
return true;
}
return false;
}

View File

@@ -36,11 +36,17 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.TaskStackBuilder;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -49,8 +55,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -75,7 +84,9 @@ import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
@@ -83,54 +94,73 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.SystemService;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.DateTimeView;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
@@ -141,12 +171,14 @@ import com.android.systemui.doze.DozeLog;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.PluginFragmentListener;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
@@ -194,11 +226,15 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
.OnChildLocationsChangedListener;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
@@ -209,48 +245,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.ActivityManager.StackId;
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.NotificationChannel;
import android.app.RemoteInput;
import android.app.TaskStackBuilder;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.Handler;
import android.service.notification.NotificationListenerService;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.IWindowManager;
import android.view.ViewAnimationUtils;
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.DejankUtils;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.util.NotificationChannels;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
@@ -718,6 +716,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private ConfigurationListener mConfigurationListener;
private boolean mReinflateNotificationsOnUserSwitched;
private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private ForegroundServiceController mForegroundServiceController;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
@@ -761,6 +760,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
updateDisplaySize();
@@ -1578,6 +1579,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
abortExistingInflation(key);
mForegroundServiceController.addNotification(notification,
mNotificationData.getImportance(key));
mPendingNotifications.put(key, shadeEntry);
}
@@ -1716,6 +1721,10 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
if (entry != null) {
mForegroundServiceController.removeNotification(entry.notification);
}
if (entry != null && entry.row != null) {
entry.row.setRemoved();
mStackScroller.cleanUpViewState(entry.row);
@@ -6766,6 +6775,9 @@ public class StatusBar extends SystemUI implements DemoMode,
entry.updateIcons(mContext, n);
inflateViews(entry, mStackScroller);
mForegroundServiceController.updateNotification(notification,
mNotificationData.getImportance(key));
boolean shouldPeek = shouldPeek(entry, notification);
boolean alertAgain = alertAgain(entry, n);
@@ -6783,6 +6795,7 @@ public class StatusBar extends SystemUI implements DemoMode,
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
}
setAreThereNotifications();
}

View File

@@ -0,0 +1,296 @@
/*
* 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.UserIdInt;
import android.app.Notification;
import android.app.NotificationManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.internal.messages.nano.SystemMessageProto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ForegroundServiceControllerTest extends SysuiTestCase {
public static @UserIdInt int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
public static @UserIdInt int USERID_TWO = USERID_ONE + 1;
private ForegroundServiceController fsc;
@Before
public void setUp() throws Exception {
fsc = new ForegroundServiceControllerImpl(mContext);
}
@Test
public void testNotificationCRUD() {
StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, "com.example.app1");
StatusBarNotification sbn_user2_app2_fg = makeMockFgSBN(USERID_TWO, "com.example.app2");
StatusBarNotification sbn_user1_app3_fg = makeMockFgSBN(USERID_ONE, "com.example.app3");
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
StatusBarNotification sbn_user2_app1 = makeMockSBN(USERID_TWO, "com.example.app1",
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
assertFalse(fsc.removeNotification(sbn_user1_app1));
assertFalse(fsc.removeNotification(sbn_user2_app1));
fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
fsc.addNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT);
fsc.addNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT);
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
fsc.addNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT);
// these are never added to the tracker
assertFalse(fsc.removeNotification(sbn_user1_app1));
assertFalse(fsc.removeNotification(sbn_user2_app1));
fsc.updateNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
fsc.updateNotification(sbn_user2_app1, NotificationManager.IMPORTANCE_DEFAULT);
// should still not be there
assertFalse(fsc.removeNotification(sbn_user1_app1));
assertFalse(fsc.removeNotification(sbn_user2_app1));
fsc.updateNotification(sbn_user2_app2_fg, NotificationManager.IMPORTANCE_DEFAULT);
fsc.updateNotification(sbn_user1_app3_fg, NotificationManager.IMPORTANCE_DEFAULT);
fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.removeNotification(sbn_user1_app3_fg));
assertFalse(fsc.removeNotification(sbn_user1_app3_fg));
assertTrue(fsc.removeNotification(sbn_user2_app2_fg));
assertFalse(fsc.removeNotification(sbn_user2_app2_fg));
assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
assertFalse(fsc.removeNotification(sbn_user1_app1_fg));
assertFalse(fsc.removeNotification(sbn_user1_app1));
assertFalse(fsc.removeNotification(sbn_user2_app1));
}
@Test
public void testDungeonPredicate() {
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android",
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
null, Notification.FLAG_NO_CLEAR);
assertTrue(fsc.isDungeonNotification(sbn_user1_dungeon));
assertFalse(fsc.isDungeonNotification(sbn_user1_app1));
}
@Test
public void testDungeonCRUD() {
StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
StatusBarNotification sbn_user1_dungeon = makeMockSBN(USERID_ONE, "android",
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
null, Notification.FLAG_NO_CLEAR);
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
fsc.addNotification(sbn_user1_dungeon, NotificationManager.IMPORTANCE_DEFAULT);
fsc.removeNotification(sbn_user1_dungeon);
assertFalse(fsc.removeNotification(sbn_user1_app1));
}
@Test
public void testNeedsDungeonAfterRemovingUnrelatedNotification() {
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
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
// nothing required yet
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
// now the app starts a fg service
fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
// add the fg notification
fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered
// remove the boring notification
fsc.removeNotification(sbn_user1_app1);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has STILL got it covered
assertTrue(fsc.removeNotification(sbn_user1_app1_fg));
assertTrue(fsc.isDungeonNeededForUser(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);
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
// no services are "running"
fsc.addNotification(makeMockDungeon(USERID_ONE, null),
NotificationManager.IMPORTANCE_DEFAULT);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG1}),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
// switch to different package
fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2}),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
fsc.updateNotification(makeMockDungeon(USERID_TWO, new String[]{PKG1}),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
assertTrue(fsc.isDungeonNeededForUser(USERID_TWO)); // finally user2 needs one too
fsc.updateNotification(makeMockDungeon(USERID_ONE, new String[]{PKG2, PKG1}),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
assertTrue(fsc.isDungeonNeededForUser(USERID_TWO));
fsc.removeNotification(makeMockDungeon(USERID_ONE, null /*unused*/));
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
assertTrue(fsc.isDungeonNeededForUser(USERID_TWO));
fsc.removeNotification(makeMockDungeon(USERID_TWO, null /*unused*/));
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
}
@Test
public void testDungeonBasic() {
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);
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
fsc.addNotification(makeMockDungeon(USERID_ONE, new String[]{ PKG1 }),
NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
fsc.addNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // app1 has got it covered
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
// let's take out the other notification and see what happens.
fsc.removeNotification(sbn_user1_app1);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
assertFalse(fsc.isDungeonNeededForUser(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;
fsc.updateNotification(sbn_user1_app1_fg_sneaky, NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
// ok, ok, we'll put it back
sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
fsc.updateNotification(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
assertTrue(fsc.removeNotification(sbn_user1_app1_fg_sneaky));
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE)); // should be required!
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
// now let's test an upgrade
fsc.addNotification(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
fsc.updateNotification(sbn_user1_app1,
NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
// remove it, make sure we're out of compliance again
assertTrue(fsc.removeNotification(sbn_user1_app1)); // was fg, should return true
assertFalse(fsc.removeNotification(sbn_user1_app1));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
assertTrue(fsc.isDungeonNeededForUser(USERID_ONE));
// finally, let's turn off the service
fsc.addNotification(makeMockDungeon(USERID_ONE, null),
NotificationManager.IMPORTANCE_DEFAULT);
assertFalse(fsc.isDungeonNeededForUser(USERID_ONE));
assertFalse(fsc.isDungeonNeededForUser(USERID_TWO));
}
private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
int flags) {
final Notification n = mock(Notification.class);
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(null);
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 makeMockFgSBN(int userid, String pkg) {
return makeMockSBN(userid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
}
private StatusBarNotification makeMockDungeon(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;
final StatusBarNotification sbn = makeMockSBN(userid, "android",
SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
null, n);
sbn.getNotification().extras = extras;
return sbn;
}
}

View File

@@ -162,7 +162,7 @@ public final class ActiveServices {
/**
* Information about an app that is currently running one or more foreground services.
* (This mapps directly to the running apps we show in the notification.)
* (This maps directly to the running apps we show in the notification.)
*/
static final class ActiveForegroundApp {
String mPackageName;
@@ -813,6 +813,7 @@ public final class ActiveServices {
String title;
String msg;
String[] pkgs;
long oldestStartTime = System.currentTimeMillis(); // now
if (active.size() == 1) {
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", active.get(0).mPackageName, null));
@@ -820,11 +821,13 @@ public final class ActiveServices {
R.string.foreground_service_app_in_background, active.get(0).mLabel);
msg = context.getString(R.string.foreground_service_tap_for_details);
pkgs = new String[] { active.get(0).mPackageName };
oldestStartTime = active.get(0).mStartTime;
} else {
intent = new Intent(Settings.ACTION_FOREGROUND_SERVICES_SETTINGS);
pkgs = new String[active.size()];
for (int i = 0; i < active.size(); i++) {
pkgs[i] = active.get(i).mPackageName;
oldestStartTime = Math.min(oldestStartTime, active.get(i).mStartTime);
}
intent.putExtra("packages", pkgs);
title = context.getString(
@@ -841,9 +844,10 @@ public final class ActiveServices {
new Notification.Builder(context,
SystemNotificationChannels.FOREGROUND_SERVICE)
.addExtras(notificationBundle)
.setSmallIcon(R.drawable.ic_check_circle_24px)
.setSmallIcon(R.drawable.stat_sys_vitals)
.setOngoing(true)
.setShowWhen(false)
.setShowWhen(oldestStartTime > 0)
.setWhen(oldestStartTime)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)