import android.annotation.NonNull;
import android.content.ComponentName;
+import android.content.IIntentSender;
import android.os.IBinder;
import android.service.voice.IVoiceInteractionSession;
* Kill foreground apps from the specified user.
*/
public abstract void killForegroundAppsForUser(int userHandle);
+
+ /**
+ * Sets how long a {@link PendingIntent} can be temporarily whitelist to by bypass restrictions
+ * such as Power Save mode.
+ */
+ public abstract void setPendingIntentWhitelistDuration(IIntentSender target, long duration);
}
reply.recycle();
}
- public void notifyLockedProfile(@UserIdInt int userId) throws RemoteException
- {
+ @Override
+ public void notifyLockedProfile(@UserIdInt int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
reply.recycle();
}
+ @Override
public void startConfirmDeviceCredentialIntent(Intent intent) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
reply.recycle();
}
+ @Override
public int sendIntentSender(IIntentSender target, int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)
throws RemoteException {
public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
+ * @hide Flag for {@link #bindService}: allows application hosting service to manage whitelists
+ * such as temporary allowing a {@code PendingIntent} to bypass Power Save mode.
+ */
+ public static final int BIND_ALLOW_WHITELIST_MANAGEMENT = 0x01000000;
+
+ /**
* @hide Flag for {@link #bindService}: Like {@link #BIND_FOREGROUND_SERVICE},
* but only applies while the device is awake.
*/
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-
import android.util.MemoryIntArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
* idle_factor (float)
* min_time_to_alarm (long)
* max_temp_app_whitelist_duration (long)
+ * notification_whitelist_duration (long)
* </pre>
*
* <p>
"mms_temp_app_whitelist_duration";
private static final String KEY_SMS_TEMP_APP_WHITELIST_DURATION =
"sms_temp_app_whitelist_duration";
+ private static final String KEY_NOTIFICATION_WHITELIST_DURATION =
+ "notification_whitelist_duration";
/**
* This is the time, after becoming inactive, that we go in to the first
*/
public long SMS_TEMP_APP_WHITELIST_DURATION;
+ /**
+ * Amount of time we would like to whitelist an app that is handling a
+ * {@link android.app.PendingIntent} triggered by a {@link android.app.Notification}.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_NOTIFICATION_WHITELIST_DURATION
+ */
+ public long NOTIFICATION_WHITELIST_DURATION;
+
private final ContentResolver mResolver;
private final boolean mHasWatch;
private final KeyValueListParser mParser = new KeyValueListParser(',');
KEY_MMS_TEMP_APP_WHITELIST_DURATION, 60 * 1000L);
SMS_TEMP_APP_WHITELIST_DURATION = mParser.getLong(
KEY_SMS_TEMP_APP_WHITELIST_DURATION, 20 * 1000L);
+ NOTIFICATION_WHITELIST_DURATION = mParser.getLong(
+ KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
}
}
pw.print(" "); pw.print(KEY_SMS_TEMP_APP_WHITELIST_DURATION); pw.print("=");
TimeUtils.formatDuration(SMS_TEMP_APP_WHITELIST_DURATION, pw);
pw.println();
+
+ pw.print(" "); pw.print(KEY_NOTIFICATION_WHITELIST_DURATION); pw.print("=");
+ TimeUtils.formatDuration(NOTIFICATION_WHITELIST_DURATION, pw);
+ pw.println();
}
}
addPowerSaveTempWhitelistAppDirectInternal(0, appId, duration, sync, reason);
}
+ public long getNotificationWhitelistDuration() {
+ return mConstants.NOTIFICATION_WHITELIST_DURATION;
+ }
+
public void setNetworkPolicyTempWhitelistCallback(Runnable callback) {
setNetworkPolicyTempWhitelistCallbackInternal(callback);
}
}
entry.first.value = timeNow + duration;
if (DEBUG) {
- Slog.d(TAG, "Adding AppId " + appId + " to temp whitelist");
+ Slog.d(TAG, "Adding AppId " + appId + " to temp whitelist. New entry: " + newEntry);
}
if (newEntry) {
// No pending timeout for the app id, post a delayed message
}
private void postTempActiveTimeoutMessage(int uid, long delay) {
+ if (DEBUG) {
+ Slog.d(TAG, "postTempActiveTimeoutMessage: uid=" + uid + ", delay=" + delay);
+ }
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TEMP_APP_WHITELIST_TIMEOUT, uid, 0),
delay);
}
void checkTempAppWhitelistTimeout(int uid) {
final long timeNow = SystemClock.elapsedRealtime();
+ if (DEBUG) {
+ Slog.d(TAG, "checkTempAppWhitelistTimeout: uid=" + uid + ", timeNow=" + timeNow);
+ }
synchronized (this) {
Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.get(uid);
if (entry == null) {
}
} else {
// Need more time
+ if (DEBUG) {
+ Slog.d(TAG, "Time to remove UID " + uid + ": " + entry.first.value);
+ }
postTempActiveTimeoutMessage(uid, entry.first.value - timeNow);
}
}
pw.println(" Print currently whitelisted apps.");
pw.println(" whitelist [package ...]");
pw.println(" Add (prefix with +) or remove (prefix with -) packages.");
+ pw.println(" tempwhitelist");
+ pw.println(" Print packages that are temporarily whitelisted.");
pw.println(" tempwhitelist [-u] [package ..]");
pw.println(" Temporarily place packages in whitelist for 10 seconds.");
}
pw.println("Failed: " + re);
}
} else {
- pw.println("At least one package name must be specified");
- return -1;
+ dumpTempWhitelistSchedule(pw, false);
}
} else {
return shell.handleDefaultCommands(cmd);
pw.println();
}
}
- size = mTempWhitelistAppIdEndTimes.size();
- if (size > 0) {
- pw.println(" Temp whitelist schedule:");
- final long timeNow = SystemClock.elapsedRealtime();
- for (int i = 0; i < size; i++) {
- pw.print(" UID=");
- pw.print(mTempWhitelistAppIdEndTimes.keyAt(i));
- pw.print(": ");
- Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.valueAt(i);
- TimeUtils.formatDuration(entry.first.value, timeNow, pw);
- pw.print(" - ");
- pw.println(entry.second);
- }
- }
+ dumpTempWhitelistSchedule(pw, true);
+
size = mTempWhitelistAppIdArray != null ? mTempWhitelistAppIdArray.length : 0;
if (size > 0) {
pw.println(" Temp whitelist app ids:");
}
pw.print(" mLightEnabled="); pw.print(mLightEnabled);
- pw.print(" mDeepEnabled="); pw.println(mDeepEnabled);
+ pw.print(" mDeepEnabled="); pw.println(mDeepEnabled);
pw.print(" mForceIdle="); pw.println(mForceIdle);
pw.print(" mMotionSensor="); pw.println(mMotionSensor);
pw.print(" mCurDisplay="); pw.println(mCurDisplay);
}
}
}
-}
+
+ void dumpTempWhitelistSchedule(PrintWriter pw, boolean printTitle) {
+ final int size = mTempWhitelistAppIdEndTimes.size();
+ if (size > 0) {
+ String prefix = "";
+ if (printTitle) {
+ pw.println(" Temp whitelist schedule:");
+ prefix = " ";
+ }
+ final long timeNow = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix);
+ pw.print("UID=");
+ pw.print(mTempWhitelistAppIdEndTimes.keyAt(i));
+ pw.print(": ");
+ Pair<MutableLong, String> entry = mTempWhitelistAppIdEndTimes.valueAt(i);
+ TimeUtils.formatDuration(entry.first.value, timeNow, pw);
+ pw.print(" - ");
+ pw.println(entry.second);
+ }
+ }
+ }
+ }
int clientLabel = 0;
PendingIntent clientIntent = null;
+ final boolean isCallerSystem = callerApp.info.uid == Process.SYSTEM_UID;
- if (callerApp.info.uid == Process.SYSTEM_UID) {
+ if (isCallerSystem) {
// Hacky kind of thing -- allow system stuff to tell us
// what they are, so we can report this elsewhere for
// others to know why certain services are running.
"BIND_TREAT_LIKE_ACTIVITY");
}
+ if ((flags & Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0 && !isCallerSystem) {
+ throw new SecurityException(
+ "Non-system caller " + caller + " (pid=" + Binder.getCallingPid()
+ + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service);
+ }
+
final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
}
if (r.binding.service.app != null) {
+ if (r.binding.service.app.whitelistManager) {
+ // Must reset flag here because on computeOomAdjLocked() the service
+ // connection will be gone...
+ r.binding.service.app.whitelistManager = false;
+ }
// This could have made the service less important.
if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
r.binding.service.app.treatLikeActivity = true;
static final boolean DEBUG_VISIBLE_BEHIND = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
+ static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false;
static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
}
}
+ /**
+ * Whitelists {@code targetUid} to temporarily bypass Power Save mode.
+ *
+ * <p>{@code callerUid} must be allowed to request such whitelist by calling
+ * {@link #addTempPowerSaveWhitelistGrantorUid(int)}.
+ */
+ void tempWhitelistAppForPowerSave(int callerPid, int callerUid, int targetUid, long duration) {
+ if (DEBUG_WHITELISTS) {
+ Slog.d(TAG, "tempWhitelistAppForPowerSave(" + callerPid + ", " + callerUid + ", "
+ + targetUid + ", " + duration + ")");
+ }
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
+ if (pr == null) {
+ Slog.w(TAG, "tempWhitelistAppForPowerSave() no ProcessRecord for pid " + callerPid);
+ return;
+ }
+ if (!pr.whitelistManager) {
+ if (DEBUG_WHITELISTS) {
+ Slog.d(TAG, "tempWhitelistAppForPowerSave() for target " + targetUid + ": pid "
+ + callerPid + " is not allowed");
+ }
+ return;
+ }
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(targetUid, duration,
+ true, "pe from uid:" + callerUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void cancelIntentSender(IIntentSender sender) {
if (!(sender instanceof PendingIntentRecord)) {
}
}
}
+
+ app.whitelistManager = false;
+
for (int conni = s.connections.size()-1;
conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
// Binding to ourself is not interesting.
continue;
}
+ if ((cr.flags & Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
+ app.whitelistManager = true;
+ }
+
if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
ProcessRecord client = cr.binding.client;
int clientAdj = computeOomAdjLocked(client, cachedAdj,
}
}
}
+
+ @Override
+ public void setPendingIntentWhitelistDuration(IIntentSender target, long duration) {
+ if (!(target instanceof PendingIntentRecord)) {
+ Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target);
+ return;
+ }
+ ((PendingIntentRecord) target).setWhitelistDuration(duration);
+ }
}
private final class SleepTokenImpl extends SleepToken {
final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
+ private long whitelistDuration = 0;
String stringName;
String lastTagPrefix;
final int flags;
final int hashCode;
final int userId;
-
+
private static final int ODD_PRIME_NUMBER = 37;
-
+
Key(int _t, String _p, ActivityRecord _a, String _w,
int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) {
type = _t;
//Slog.i(ActivityManagerService.TAG, this + " hashCode=0x"
// + Integer.toHexString(hashCode));
}
-
+
public boolean equals(Object otherObj) {
if (otherObj == null) {
return false;
ref = new WeakReference<PendingIntentRecord>(this);
}
+ void setWhitelistDuration(long duration) {
+ this.whitelistDuration = duration;
+ this.stringName = null;
+ }
+
public void send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
sendInner(code, intent, resolvedType, finishedReceiver,
if (intent != null) intent.setDefusable(true);
if (options != null) options.setDefusable(true);
+ if (whitelistDuration > 0 && !canceled) {
+ // Must call before acquiring the lock. It's possible the method return before sending
+ // the intent due to some validations inside the lock, in which case the UID shouldn't
+ // be whitelisted, but since the whitelist is temporary, that would be ok.
+ owner.tempWhitelistAppForPowerSave(Binder.getCallingPid(), Binder.getCallingUid(), uid,
+ whitelistDuration);
+ }
+
synchronized (owner) {
final ActivityContainer activityContainer = (ActivityContainer)container;
if (activityContainer != null && activityContainer.mParentActivity != null &&
}
}
}
-
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
pw.print(prefix); pw.print("sent="); pw.print(sent);
pw.print(" canceled="); pw.println(canceled);
}
+ pw.print(prefix); pw.println("whitelistDuration="); pw.println(whitelistDuration);
}
public String toString() {
sb.append(key.packageName);
sb.append(' ');
sb.append(key.typeName());
+ if (whitelistDuration > 0) {
+ sb.append( " (whitelistDuration: ").append(whitelistDuration).append("ms)");
+ }
sb.append('}');
return stringName = sb.toString();
}
// Process is currently hosting a backup agent for backup or restore
public boolean inFullBackup;
+ // App is allowed to manage whitelists such as temporary Power Save mode whitelist.
+ boolean whitelistManager;
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
}
pw.println();
}
+ if (whitelistManager) {
+ pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager);
+ }
if (activities.size() > 0) {
pw.print(prefix); pw.println("Activities:");
for (int i=0; i<activities.size(); i++) {
package com.android.server.notification;
+import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
-import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
};
if (!mContext.bindServiceAsUser(intent,
serviceConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT,
new UserHandle(userid))) {
mServicesBinding.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_NONE;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
+import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+ " id=" + id + " notification=" + notification);
}
+ markAsSentFromNotification(notification);
+
// Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
idOut[0] = id;
}
+ private static void markAsSentFromNotification(Notification notification) {
+ final ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final long duration = LocalServices.getService(DeviceIdleController.LocalService.class)
+ .getNotificationWhitelistDuration();
+
+ int size = 0;
+ if (notification.contentIntent != null) {
+ am.setPendingIntentWhitelistDuration(notification.contentIntent.getTarget(), duration);
+ }
+ if (notification.deleteIntent != null) {
+ am.setPendingIntentWhitelistDuration(notification.deleteIntent.getTarget(), duration);
+ }
+ if (notification.fullScreenIntent != null) {
+ am.setPendingIntentWhitelistDuration(notification.fullScreenIntent.getTarget(),
+ duration);
+ }
+ if (notification.actions != null) {
+ for (Notification.Action action: notification.actions) {
+ am.setPendingIntentWhitelistDuration(action.actionIntent.getTarget(), duration);
+ setPendingIntentWhitelistDuration(am, duration, action.getExtras());
+ final RemoteInput[] remoteInputs = action.getRemoteInputs();
+ if (remoteInputs != null) {
+ for (RemoteInput remoteInput : remoteInputs) {
+ setPendingIntentWhitelistDuration(am, duration, remoteInput.getExtras());
+ }
+ }
+ }
+ }
+ }
+
+ private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration,
+ Bundle extras) {
+ for (String key : extras.keySet()) {
+ setPendingIntentWhitelistDuration(am, duration, extras.getParcelable(key));
+ final Parcelable[] parcelableArray = extras.getParcelableArray(key);
+ if (parcelableArray != null) {
+ for (Parcelable parcelable: parcelableArray) {
+ setPendingIntentWhitelistDuration(am, duration, parcelable);
+ }
+ }
+ final ArrayList<Parcelable> parcelableList = extras.getParcelableArrayList(key);
+ if (parcelableList != null) {
+ for (Parcelable parcelable: parcelableList) {
+ setPendingIntentWhitelistDuration(am, duration, parcelable);
+ }
+ }
+ }
+ }
+
+ private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration,
+ Parcelable parcelable) {
+ if (parcelable instanceof PendingIntent) {
+ am.setPendingIntentWhitelistDuration(((PendingIntent) parcelable).getTarget(),
+ duration);
+ }
+ }
+
private class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;