Merge changes from topic 'dungeon' into oc-dev
* changes: Hide redundant foreground service notifications. Updates to Dianne's Dungeon.
This commit is contained in:
committed by
Android (Google) Code Review
commit
36a673a7d7
@@ -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);
|
||||
}
|
||||
|
||||
29
core/res/res/drawable/stat_sys_vitals.xml
Normal file
29
core/res/res/drawable/stat_sys_vitals.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user