Merge "Brightness mirror should use QS theme"
diff --git a/Android.mk b/Android.mk
index 08a8b64..f4a9ff9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -521,10 +521,12 @@
telephony/java/com/android/ims/internal/IImsEcbmListener.aidl \
telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl \
+ telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl \
telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
+ telephony/java/com/android/ims/internal/IImsRcsFeature.aidl \
telephony/java/com/android/ims/internal/IImsService.aidl \
telephony/java/com/android/ims/internal/IImsServiceController.aidl \
- telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
+ telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl \
telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
telephony/java/com/android/ims/internal/IImsUt.aidl \
telephony/java/com/android/ims/internal/IImsUtListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index dca7528..f59bbf7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2777,6 +2777,7 @@
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5616,6 +5617,7 @@
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5628,8 +5630,12 @@
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6371,6 +6377,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6884,6 +6891,7 @@
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -23045,6 +23053,7 @@
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -25854,7 +25863,6 @@
method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -30830,6 +30838,7 @@
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+ field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
field public static final java.lang.String EXTRA_HEALTH = "health";
field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/api/system-current.txt b/api/system-current.txt
index aa3a1e2..67e4586 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2919,6 +2919,7 @@
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5825,6 +5826,7 @@
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5837,8 +5839,12 @@
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6599,6 +6605,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -7327,6 +7334,7 @@
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -24939,6 +24947,7 @@
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -28100,7 +28109,6 @@
method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -33562,6 +33570,7 @@
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+ field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
field public static final java.lang.String EXTRA_HEALTH = "health";
field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/api/test-current.txt b/api/test-current.txt
index 57b5222..2eca72f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2777,6 +2777,7 @@
field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+ field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5647,6 +5648,7 @@
method public android.content.ComponentName getEffectsSuppressor();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+ method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5659,8 +5661,12 @@
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+ field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
field public static final int IMPORTANCE_HIGH = 4; // 0x4
field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6440,6 +6446,7 @@
method public boolean installCaCert(android.content.ComponentName, byte[]);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+ method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6958,6 +6965,7 @@
method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+ method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -23248,6 +23256,7 @@
public static final class MediaMuxer.OutputFormat {
field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+ field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
}
@@ -31033,6 +31042,7 @@
field public static final int BATTERY_STATUS_FULL = 5; // 0x5
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+ field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
field public static final java.lang.String EXTRA_HEALTH = "health";
field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/cmds/pm/Android.mk b/cmds/pm/Android.mk
index 6a03def..960c805 100644
--- a/cmds/pm/Android.mk
+++ b/cmds/pm/Android.mk
@@ -3,14 +3,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := pmlib
-LOCAL_MODULE_STEM := pm
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := pm
-LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := pm
-LOCAL_REQUIRED_MODULES := pmlib
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 53f85b2..4d1f945 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,8 +1,2 @@
#!/system/bin/sh
-# Script to start "pm" on the device, which has a very rudimentary
-# shell.
-#
-base=/system
-export CLASSPATH=$base/framework/pm.jar
-exec app_process $base/bin com.android.commands.pm.Pm "$@"
-
+cmd package "$@"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880..0000000
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2007 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.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
- private static final String TAG = "Pm";
- private static final String STDIN_PATH = "-";
-
- IPackageManager mPm;
- IPackageInstaller mInstaller;
- IUserManager mUm;
- IAccountManager mAm;
-
- private String[] mArgs;
- private int mNextArg;
- private String mCurArgData;
-
- private static final String PM_NOT_RUNNING_ERR =
- "Error: Could not access the Package Manager. Is the system running?";
-
- public static void main(String[] args) {
- int exitCode = 1;
- try {
- exitCode = new Pm().run(args);
- } catch (Exception e) {
- Log.e(TAG, "Error", e);
- System.err.println("Error: " + e);
- if (e instanceof RemoteException) {
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- }
- System.exit(exitCode);
- }
-
- public int run(String[] args) throws RemoteException {
- if (args.length < 1) {
- return runShellCommand("package", mArgs);
- }
- mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
- mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
- mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
- if (mPm == null) {
- System.err.println(PM_NOT_RUNNING_ERR);
- return 1;
- }
- mInstaller = mPm.getPackageInstaller();
-
- mArgs = args;
- String op = args[0];
- mNextArg = 1;
-
- if ("install".equals(op)) {
- return runInstall();
- }
-
- if ("install-create".equals(op)) {
- return runInstallCreate();
- }
-
- if ("install-write".equals(op)) {
- return runInstallWrite();
- }
-
- if ("install-commit".equals(op)) {
- return runInstallCommit();
- }
-
- if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
- return runInstallAbandon();
- }
-
- return runShellCommand("package", mArgs);
- }
-
- static final class MyShellCallback extends ShellCallback {
- @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
- String mode) {
- File file = new File(path);
- final ParcelFileDescriptor fd;
- try {
- fd = ParcelFileDescriptor.open(file,
- ParcelFileDescriptor.MODE_CREATE |
- ParcelFileDescriptor.MODE_TRUNCATE |
- ParcelFileDescriptor.MODE_WRITE_ONLY);
- } catch (FileNotFoundException e) {
- String msg = "Unable to open file " + path + ": " + e;
- System.err.println(msg);
- throw new IllegalArgumentException(msg);
- }
- if (seLinuxContext != null) {
- final String tcon = SELinux.getFileContext(file.getAbsolutePath());
- if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
- try {
- fd.close();
- } catch (IOException e) {
- }
- String msg = "System server has no access to file context " + tcon;
- System.err.println(msg + " (from path " + file.getAbsolutePath()
- + ", context " + seLinuxContext + ")");
- throw new IllegalArgumentException(msg);
- }
- }
- return fd;
- }
- }
-
- private int runShellCommand(String serviceName, String[] args) {
- final HandlerThread handlerThread = new HandlerThread("results");
- handlerThread.start();
- try {
- ServiceManager.getService(serviceName).shellCommand(
- FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
- args, new MyShellCallback(),
- new ResultReceiver(new Handler(handlerThread.getLooper())));
- return 0;
- } catch (RemoteException e) {
- e.printStackTrace();
- } finally {
- handlerThread.quitSafely();
- }
- return -1;
- }
-
- private static class LocalIntentReceiver {
- private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
- private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
- @Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
- IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
- try {
- mResult.offer(intent, 5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- };
-
- public IntentSender getIntentSender() {
- return new IntentSender((IIntentSender) mLocalSender);
- }
-
- public Intent getResult() {
- try {
- return mResult.take();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- private int translateUserId(int userId, String logContext) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, logContext, "pm command");
- }
-
- private static String checkAbiArgument(String abi) {
- if (TextUtils.isEmpty(abi)) {
- throw new IllegalArgumentException("Missing ABI argument");
- }
- if ("-".equals(abi)) {
- return abi;
- }
- final String[] supportedAbis = Build.SUPPORTED_ABIS;
- for (String supportedAbi : supportedAbis) {
- if (supportedAbi.equals(abi)) {
- return abi;
- }
- }
- throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
- }
-
- /*
- * Keep this around to support existing users of the "pm install" command that may not be
- * able to be updated [or, at least informed the API has changed] such as ddmlib.
- *
- * Moving the implementation of "pm install" to "cmd package install" changes the executing
- * context. Instead of being a stand alone process, "cmd package install" runs in the
- * system_server process. Due to SELinux rules, system_server cannot access many directories;
- * one of which being the package install staging directory [/data/local/tmp].
- *
- * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
- */
- private int runInstall() throws RemoteException {
- long startedTime = SystemClock.elapsedRealtime();
- final InstallParams params = makeInstallParams();
- final String inPath = nextArg();
- if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
- File file = new File(inPath);
- if (file.isFile()) {
- try {
- ApkLite baseApk = PackageParser.parseApkLite(file, 0);
- PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
- null, null);
- params.sessionParams.setSize(
- PackageHelper.calculateInstalledSize(pkgLite,
- params.sessionParams.abiOverride));
- } catch (PackageParserException | IOException e) {
- System.err.println("Error: Failed to parse APK file: " + e);
- return 1;
- }
- } else {
- System.err.println("Error: Can't open non-file: " + inPath);
- return 1;
- }
- }
-
- final int sessionId = doCreateSession(params.sessionParams,
- params.installerPackageName, params.userId);
-
- try {
- if (inPath == null && params.sessionParams.sizeBytes == -1) {
- System.err.println("Error: must either specify a package size or an APK file");
- return 1;
- }
- if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
- if (status.second != PackageInstaller.STATUS_SUCCESS) {
- return 1;
- }
- Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
- - startedTime) + " ms");
- System.out.println("Success");
- return 0;
- } finally {
- try {
- mInstaller.abandonSession(sessionId);
- } catch (Exception ignore) {
- }
- }
- }
-
- private int runInstallAbandon() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doAbandonSession(sessionId, true /*logSuccess*/);
- }
-
- private int runInstallCommit() throws RemoteException {
- final int sessionId = Integer.parseInt(nextArg());
- return doCommitSession(sessionId, true /*logSuccess*/).second;
- }
-
- private int runInstallCreate() throws RemoteException {
- final InstallParams installParams = makeInstallParams();
- final int sessionId = doCreateSession(installParams.sessionParams,
- installParams.installerPackageName, installParams.userId);
-
- // NOTE: adb depends on parsing this string
- System.out.println("Success: created install session [" + sessionId + "]");
- return PackageInstaller.STATUS_SUCCESS;
- }
-
- private int runInstallWrite() throws RemoteException {
- long sizeBytes = -1;
-
- String opt;
- while ((opt = nextOption()) != null) {
- if (opt.equals("-S")) {
- sizeBytes = Long.parseLong(nextArg());
- } else {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
-
- final int sessionId = Integer.parseInt(nextArg());
- final String splitName = nextArg();
- final String path = nextArg();
- return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
- }
-
- private static class InstallParams {
- SessionParams sessionParams;
- String installerPackageName;
- int userId = UserHandle.USER_ALL;
- }
-
- private InstallParams makeInstallParams() {
- final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
- final InstallParams params = new InstallParams();
- params.sessionParams = sessionParams;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-l":
- sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
- break;
- case "-r":
- sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
- break;
- case "-i":
- params.installerPackageName = nextArg();
- if (params.installerPackageName == null) {
- throw new IllegalArgumentException("Missing installer package");
- }
- break;
- case "-t":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
- break;
- case "-s":
- sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
- break;
- case "-f":
- sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
- break;
- case "-d":
- sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
- break;
- case "-g":
- sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
- break;
- case "--dont-kill":
- sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
- break;
- case "--originating-uri":
- sessionParams.originatingUri = Uri.parse(nextOptionData());
- break;
- case "--referrer":
- sessionParams.referrerUri = Uri.parse(nextOptionData());
- break;
- case "-p":
- sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing inherit package name");
- }
- break;
- case "--pkg":
- sessionParams.appPackageName = nextOptionData();
- if (sessionParams.appPackageName == null) {
- throw new IllegalArgumentException("Missing package name");
- }
- break;
- case "-S":
- final long sizeBytes = Long.parseLong(nextOptionData());
- if (sizeBytes <= 0) {
- throw new IllegalArgumentException("Size must be positive");
- }
- sessionParams.setSize(sizeBytes);
- break;
- case "--abi":
- sessionParams.abiOverride = checkAbiArgument(nextOptionData());
- break;
- case "--ephemeral":
- case "--instant":
- sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
- break;
- case "--full":
- sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
- break;
- case "--user":
- params.userId = UserHandle.parseUserArg(nextOptionData());
- break;
- case "--install-location":
- sessionParams.installLocation = Integer.parseInt(nextOptionData());
- break;
- case "--force-uuid":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
- sessionParams.volumeUuid = nextOptionData();
- if ("internal".equals(sessionParams.volumeUuid)) {
- sessionParams.volumeUuid = null;
- }
- break;
- case "--force-sdk":
- sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
- break;
- default:
- throw new IllegalArgumentException("Unknown option " + opt);
- }
- }
- return params;
- }
-
- private int doCreateSession(SessionParams params, String installerPackageName, int userId)
- throws RemoteException {
- userId = translateUserId(userId, "runInstallCreate");
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- params.installFlags |= PackageManager.INSTALL_ALL_USERS;
- }
-
- final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
- return sessionId;
- }
-
- private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
- boolean logSuccess) throws RemoteException {
- if (STDIN_PATH.equals(inPath)) {
- inPath = null;
- } else if (inPath != null) {
- final File file = new File(inPath);
- if (file.isFile()) {
- sizeBytes = file.length();
- }
- }
-
- final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
- PackageInstaller.Session session = null;
- InputStream in = null;
- OutputStream out = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- if (inPath != null) {
- in = new FileInputStream(inPath);
- } else {
- in = new SizedInputStream(System.in, sizeBytes);
- }
- out = session.openWrite(splitName, 0, sizeBytes);
-
- int total = 0;
- byte[] buffer = new byte[1024 * 1024];
- int c;
- while ((c = in.read(buffer)) != -1) {
- total += c;
- out.write(buffer, 0, c);
-
- if (info.sizeBytes > 0) {
- final float fraction = ((float) c / (float) info.sizeBytes);
- session.addProgress(fraction);
- }
- }
- session.fsync(out);
-
- if (logSuccess) {
- System.out.println("Success: streamed " + total + " bytes");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } catch (IOException e) {
- System.err.println("Error: failed to write; " + e.getMessage());
- return PackageInstaller.STATUS_FAILURE;
- } finally {
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(session);
- }
- }
-
- private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
- throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(
- mInstaller.openSession(sessionId));
-
- final LocalIntentReceiver receiver = new LocalIntentReceiver();
- session.commit(receiver.getIntentSender());
-
- final Intent result = receiver.getResult();
- final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_FAILURE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- if (logSuccess) {
- System.out.println("Success");
- }
- } else {
- System.err.println("Failure ["
- + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
- }
- return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
- PackageInstaller.Session session = null;
- try {
- session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- session.abandon();
- if (logSuccess) {
- System.out.println("Success");
- }
- return PackageInstaller.STATUS_SUCCESS;
- } finally {
- IoUtils.closeQuietly(session);
- }
- }
-
- class LocalPackageInstallObserver extends PackageInstallObserver {
- boolean finished;
- int result;
- String extraPermission;
- String extraPackage;
-
- @Override
- public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
- synchronized (this) {
- finished = true;
- result = status;
- if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
- extraPermission = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
- extraPackage = extras.getString(
- PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
- }
- notifyAll();
- }
- }
- }
-
- private static boolean isNumber(String s) {
- try {
- Integer.parseInt(s);
- } catch (NumberFormatException nfe) {
- return false;
- }
- return true;
- }
-
- static class ClearCacheObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
-
- }
-
- static class ClearDataObserver extends IPackageDataObserver.Stub {
- boolean finished;
- boolean result;
-
- @Override
- public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
- synchronized (this) {
- finished = true;
- result = succeeded;
- notifyAll();
- }
- }
- }
-
- /**
- * Displays the package file for a package.
- * @param pckg
- */
- private int displayPackageFilePath(String pckg, int userId) {
- try {
- PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
- if (info != null && info.applicationInfo != null) {
- System.out.print("package:");
- System.out.println(info.applicationInfo.sourceDir);
- if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
- for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- System.out.print("package:");
- System.out.println(splitSourceDir);
- }
- }
- return 0;
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(PM_NOT_RUNNING_ERR);
- }
- return 1;
- }
-
- private String nextOption() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- if (!arg.startsWith("-")) {
- return null;
- }
- mNextArg++;
- if (arg.equals("--")) {
- return null;
- }
- if (arg.length() > 1 && arg.charAt(1) != '-') {
- if (arg.length() > 2) {
- mCurArgData = arg.substring(2);
- return arg.substring(0, 2);
- } else {
- mCurArgData = null;
- return arg;
- }
- }
- mCurArgData = null;
- return arg;
- }
-
- private String nextOptionData() {
- if (mCurArgData != null) {
- return mCurArgData;
- }
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String data = mArgs[mNextArg];
- mNextArg++;
- return data;
- }
-
- private String nextArg() {
- if (mNextArg >= mArgs.length) {
- return null;
- }
- String arg = mArgs[mNextArg];
- mNextArg++;
- return arg;
- }
-
- private static int showUsage() {
- System.err.println("usage: pm path [--user USER_ID] PACKAGE");
- System.err.println(" pm dump PACKAGE");
- System.err.println(" pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
- System.err.println(" pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
- System.err.println(" [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
- System.err.println(" [--originating-uri URI] [---referrer URI]");
- System.err.println(" [--abi ABI_NAME] [--force-sdk]");
- System.err.println(" [--preload] [--instantapp] [--full] [--dont-kill]");
- System.err.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
- System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
- System.err.println(" pm install-commit SESSION_ID");
- System.err.println(" pm install-abandon SESSION_ID");
- System.err.println(" pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
- System.err.println(" pm set-installer PACKAGE INSTALLER");
- System.err.println(" pm move-package PACKAGE [internal|UUID]");
- System.err.println(" pm move-primary-storage [internal|UUID]");
- System.err.println(" pm clear [--user USER_ID] PACKAGE");
- System.err.println(" pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
- System.err.println(" pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
- System.err.println(" pm grant [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm revoke [--user USER_ID] PACKAGE PERMISSION");
- System.err.println(" pm reset-permissions");
- System.err.println(" pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
- System.err.println(" pm get-app-link [--user USER_ID] PACKAGE");
- System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
- System.err.println(" pm get-install-location");
- System.err.println(" pm set-permission-enforced PERMISSION [true|false]");
- System.err.println(" pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
- System.err.println(" pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
- System.err.println(" pm remove-user USER_ID");
- System.err.println(" pm get-max-users");
- System.err.println("");
- System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
- System.err.println(" to display the new commands.");
- System.err.println("");
- System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
- System.err.println("");
- System.err.println("pm dump: print system state associated with the given PACKAGE.");
- System.err.println("");
- System.err.println("pm install: install a single legacy package");
- System.err.println("pm install-create: create an install session");
- System.err.println(" -l: forward lock application");
- System.err.println(" -r: allow replacement of existing application");
- System.err.println(" -t: allow test packages");
- System.err.println(" -i: specify package name of installer owning the app");
- System.err.println(" -s: install application on sdcard");
- System.err.println(" -f: install application on internal flash");
- System.err.println(" -d: allow version code downgrade (debuggable packages only)");
- System.err.println(" -p: partial application install (new split on top of existing pkg)");
- System.err.println(" -g: grant all runtime permissions");
- System.err.println(" -S: size in bytes of entire session");
- System.err.println(" --dont-kill: installing a new feature split, don't kill running app");
- System.err.println(" --originating-uri: set URI where app was downloaded from");
- System.err.println(" --referrer: set URI that instigated the install of the app");
- System.err.println(" --pkg: specify expected package name of app being installed");
- System.err.println(" --abi: override the default ABI of the platform");
- System.err.println(" --instantapp: cause the app to be installed as an ephemeral install app");
- System.err.println(" --full: cause the app to be installed as a non-ephemeral full app");
- System.err.println(" --install-location: force the install location:");
- System.err.println(" 0=auto, 1=internal only, 2=prefer external");
- System.err.println(" --force-uuid: force install on to disk volume with given UUID");
- System.err.println(" --force-sdk: allow install even when existing app targets platform");
- System.err.println(" codename but new one targets a final API level");
- System.err.println("");
- System.err.println("pm install-write: write a package into existing session; path may");
- System.err.println(" be '-' to read from stdin");
- System.err.println(" -S: size in bytes of package, required for stdin");
- System.err.println("");
- System.err.println("pm install-commit: perform install of fully staged session");
- System.err.println("pm install-abandon: abandon session");
- System.err.println("");
- System.err.println("pm set-installer: set installer package name");
- System.err.println("");
- System.err.println("pm uninstall: removes a package from the system. Options:");
- System.err.println(" -k: keep the data and cache directories around after package removal.");
- System.err.println("");
- System.err.println("pm clear: deletes all data associated with a package.");
- System.err.println("");
- System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
- System.err.println(" these commands change the enabled state of a given package or");
- System.err.println(" component (written as \"package/class\").");
- System.err.println("");
- System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
- System.err.println(" to apps. The permissions must be declared as used in the app's");
- System.err.println(" manifest, be runtime permissions (protection level dangerous),");
- System.err.println(" and the app targeting SDK greater than Lollipop MR1.");
- System.err.println("");
- System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
- System.err.println("");
- System.err.println("pm get-install-location: returns the current install location.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm set-install-location: changes the default install location.");
- System.err.println(" NOTE: this is only intended for debugging; using this can cause");
- System.err.println(" applications to break and other undersireable behavior.");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
- System.err.println("");
- System.err.println("pm trim-caches: trim cache files to reach the given free space.");
- System.err.println("");
- System.err.println("pm create-user: create a new user with the given USER_NAME,");
- System.err.println(" printing the new user identifier of the user.");
- System.err.println("");
- System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
- System.err.println(" deleting all data associated with that user");
- System.err.println("");
- return 1;
- }
-}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 1b2f9da..fd9465d 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -55,7 +55,8 @@
src/storage/DropboxWriter.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
- src/stats_util.cpp
+ src/stats_util.cpp \
+ src/guardrail/MemoryLeakTrackUtil.cpp
statsd_common_c_includes := \
$(LOCAL_PATH)/src \
@@ -82,7 +83,8 @@
libhidltransport \
libhwbinder \
android.hardware.power@1.0 \
- android.hardware.power@1.1
+ android.hardware.power@1.1 \
+ libmemunreachable
# =========
# statsd
@@ -178,3 +180,7 @@
statsd_common_c_includes:=
include $(BUILD_NATIVE_TEST)
+
+##############################
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1a056df..65b3da1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,10 +17,11 @@
#define DEBUG true
#include "Log.h"
-#include "android-base/stringprintf.h"
#include "StatsService.h"
+#include "android-base/stringprintf.h"
#include "config/ConfigKey.h"
#include "config/ConfigManager.h"
+#include "guardrail/MemoryLeakTrackUtil.h"
#include "storage/DropboxReader.h"
#include <android-base/file.h>
@@ -226,6 +227,10 @@
if (!args[0].compare(String8("clear-config"))) {
return cmd_remove_config_files(out);
}
+
+ if (!args[0].compare(String8("meminfo"))) {
+ return cmd_dump_memory_info(out);
+ }
}
print_cmd_help(out);
@@ -238,6 +243,15 @@
"[timestamp_nsec_optional]\n");
fprintf(out, "\n");
fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmd stats meminfo\n");
+ fprintf(out, "\n");
+ fprintf(out, " Prints the malloc debug information. You need to run the following first: \n");
+ fprintf(out, " # adb shell stop\n");
+ fprintf(out, " # adb shell setprop libc.debug.malloc.program statsd \n");
+ fprintf(out, " # adb shell setprop libc.debug.malloc.options backtrace \n");
+ fprintf(out, " # adb shell start\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
fprintf(out, "\n");
fprintf(out, " Prints the UID, app name, version mapping.\n");
@@ -507,6 +521,13 @@
return NO_ERROR;
}
+status_t StatsService::cmd_dump_memory_info(FILE* out) {
+ std::string s = dumpMemInfo(100);
+ fprintf(out, "Memory Info\n");
+ fprintf(out, "%s", s.c_str());
+ return NO_ERROR;
+}
+
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
const vector<String16>& app) {
if (DEBUG) ALOGD("StatsService::informAllUidData was called");
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 4d768f6..47f0bb6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -160,6 +160,11 @@
*/
status_t cmd_remove_config_files(FILE* out);
+ /*
+ * Dump memory usage by statsd.
+ */
+ status_t cmd_dump_memory_info(FILE* out);
+
/**
* Update a configuration.
*/
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57a92b6..20e7c60 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -74,6 +74,7 @@
ActivityForegroundStateChanged activity_foreground_state_changed = 42;
IsolatedUidChanged isolated_uid_changed = 43;
PacketWakeupOccurred packet_wakeup_occurred = 44;
+ DropboxErrorChanged dropbox_error_changed = 45;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -716,6 +717,34 @@
}
/**
+ * Logs when an error is written to dropbox.
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DropboxErrorChanged {
+ // The uid if available. -1 means not available.
+ optional int32 uid = 1;
+
+ // Tag used when recording this error to dropbox. Contains data_ or system_ prefix.
+ optional string tag = 2;
+
+ // The name of the process.
+ optional string process_name = 3;
+
+ // The pid if available. -1 means not available.
+ optional int32 pid = 4;
+
+ // 1 indicates is instant app. -1 indicates Not applicable.
+ optional int32 is_instant_app = 5;
+
+ // The activity name if available.
+ optional string activity_name = 6;
+
+ // 1 indicates in foreground. -1 indicates not available.
+ optional int32 is_foreground = 7;
+}
+
+/**
* Pulls bytes transferred via wifi (Sum of foreground and background usage).
*
* Pulled from:
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 2125609..0c9252e 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -232,7 +232,7 @@
static StatsdConfig build_fake_config() {
// HACK: Hard code a test metric for counting screen on events...
StatsdConfig config;
- config.set_name("12345");
+ config.set_name("CONFIG_12345");
int WAKE_LOCK_TAG_ID = 1111; // put a fake id here to make testing easier.
int WAKE_LOCK_UID_KEY_ID = 1;
@@ -263,20 +263,21 @@
// Count Screen ON events.
CountMetric* metric = config.add_count_metric();
- metric->set_name("1");
+ metric->set_name("METRIC_1");
metric->set_what("SCREEN_TURNED_ON");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
// Anomaly threshold for screen-on count.
Alert* alert = config.add_alert();
- alert->set_name("1");
+ alert->set_name("ALERT_1");
+ alert->set_metric_name("METRIC_1");
alert->set_number_of_buckets(6);
alert->set_trigger_if_sum_gt(10);
alert->set_refractory_period_secs(30);
// Count process state changes, slice by uid.
metric = config.add_count_metric();
- metric->set_name("2");
+ metric->set_name("METRIC_2");
metric->set_what("PROCESS_STATE_CHANGE");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
KeyMatcher* keyMatcher = metric->add_dimension();
@@ -284,14 +285,15 @@
// Anomaly threshold for background count.
alert = config.add_alert();
- alert->set_name("2");
+ alert->set_name("ALERT_2");
+ alert->set_metric_name("METRIC_2");
alert->set_number_of_buckets(4);
alert->set_trigger_if_sum_gt(30);
alert->set_refractory_period_secs(20);
// Count process state changes, slice by uid, while SCREEN_IS_OFF
metric = config.add_count_metric();
- metric->set_name("3");
+ metric->set_name("METRIC_3");
metric->set_what("PROCESS_STATE_CHANGE");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
keyMatcher = metric->add_dimension();
@@ -300,7 +302,7 @@
// Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
metric = config.add_count_metric();
- metric->set_name("4");
+ metric->set_name("METRIC_4");
metric->set_what("APP_GET_WL");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
keyMatcher = metric->add_dimension();
@@ -313,7 +315,7 @@
// Duration of an app holding any wl, while screen on and app in background, slice by uid
DurationMetric* durationMetric = config.add_duration_metric();
- durationMetric->set_name("5");
+ durationMetric->set_name("METRIC_5");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
keyMatcher = durationMetric->add_dimension();
@@ -327,7 +329,7 @@
// max Duration of an app holding any wl, while screen on and app in background, slice by uid
durationMetric = config.add_duration_metric();
- durationMetric->set_name("6");
+ durationMetric->set_name("METRIC_6");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
keyMatcher = durationMetric->add_dimension();
@@ -341,7 +343,7 @@
// Duration of an app holding any wl, while screen on and app in background
durationMetric = config.add_duration_metric();
- durationMetric->set_name("7");
+ durationMetric->set_name("METRIC_7");
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
@@ -353,14 +355,14 @@
// Duration of screen on time.
durationMetric = config.add_duration_metric();
- durationMetric->set_name("8");
+ durationMetric->set_name("METRIC_8");
durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
durationMetric->set_what("SCREEN_IS_ON");
// Value metric to count KERNEL_WAKELOCK when screen turned on
ValueMetric* valueMetric = config.add_value_metric();
- valueMetric->set_name("6");
+ valueMetric->set_name("METRIC_6");
valueMetric->set_what("KERNEL_WAKELOCK");
valueMetric->set_value_field(1);
valueMetric->set_condition("SCREEN_IS_ON");
@@ -371,12 +373,12 @@
// Add an EventMetric to log process state change events.
EventMetric* eventMetric = config.add_event_metric();
- eventMetric->set_name("9");
+ eventMetric->set_name("METRIC_9");
eventMetric->set_what("SCREEN_TURNED_ON");
// Add an GaugeMetric.
GaugeMetric* gaugeMetric = config.add_gauge_metric();
- gaugeMetric->set_name("10");
+ gaugeMetric->set_name("METRIC_10");
gaugeMetric->set_what("DEVICE_TEMPERATURE");
gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
new file mode 100644
index 0000000..e1947c4
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include <sstream>
+#include "MemoryLeakTrackUtil.h"
+
+/*
+ * The code here originally resided in MediaPlayerService.cpp
+ */
+
+// Figure out the abi based on defined macros.
+#if defined(__arm__)
+#define ABI_STRING "arm"
+#elif defined(__aarch64__)
+#define ABI_STRING "arm64"
+#elif defined(__mips__) && !defined(__LP64__)
+#define ABI_STRING "mips"
+#elif defined(__mips__) && defined(__LP64__)
+#define ABI_STRING "mips64"
+#elif defined(__i386__)
+#define ABI_STRING "x86"
+#elif defined(__x86_64__)
+#define ABI_STRING "x86_64"
+#else
+#error "Unsupported ABI"
+#endif
+
+extern std::string backtrace_string(const uintptr_t* frames, size_t frame_count);
+
+namespace android {
+namespace os {
+namespace statsd {
+
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize,
+ size_t* totalMemory, size_t* backtraceSize);
+
+extern "C" void free_malloc_leak_info(uint8_t* info);
+
+std::string dumpMemInfo(size_t limit) {
+ uint8_t* info;
+ size_t overallSize;
+ size_t infoSize;
+ size_t totalMemory;
+ size_t backtraceSize;
+ get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
+
+ size_t count;
+ if (info == nullptr || overallSize == 0 || infoSize == 0 ||
+ (count = overallSize / infoSize) == 0) {
+ ALOGD("no malloc info, libc.debug.malloc.program property should be set");
+ return std::string();
+ }
+
+ std::ostringstream oss;
+ oss << totalMemory << " bytes in " << count << " allocations\n";
+ oss << " ABI: '" ABI_STRING "'"
+ << "\n\n";
+ if (count > limit) count = limit;
+
+ // The memory is sorted based on total size which is useful for finding
+ // worst memory offenders. For diffs, sometimes it is preferable to sort
+ // based on the backtrace.
+ for (size_t i = 0; i < count; i++) {
+ struct AllocEntry {
+ size_t size; // bit 31 is set if this is zygote allocated memory
+ size_t allocations;
+ uintptr_t backtrace[];
+ };
+
+ const AllocEntry* const e = (AllocEntry*)(info + i * infoSize);
+
+ oss << (e->size * e->allocations) << " bytes ( " << e->size << " bytes * " << e->allocations
+ << " allocations )\n";
+ oss << backtrace_string(e->backtrace, backtraceSize) << "\n";
+ }
+ oss << "\n";
+ free_malloc_leak_info(info);
+ return oss.str();
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
new file mode 100644
index 0000000..444ed92
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.
+ */
+#pragma once
+
+#include <iostream>
+
+namespace android {
+namespace os {
+namespace statsd {
+/*
+ * Dump the heap memory of the calling process, sorted by total size
+ * (allocation size * number of allocations).
+ *
+ * limit is the number of unique allocations to return.
+ */
+extern std::string dumpMemInfo(size_t limit);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index d47bd4f..a5ce389 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -133,7 +133,7 @@
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index b0a97b1..a32e0cb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -163,6 +163,14 @@
ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
+
+ // If there is no duration bucket info for this key, don't include it in the report.
+ // For example, duration started, but condition is never turned to true.
+ // TODO: Only add the key to the map when we add duration buckets info for it.
+ if (pair.second.size() == 0) {
+ continue;
+ }
+
long long wrapperToken =
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -172,7 +180,7 @@
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
@@ -203,7 +211,6 @@
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
-
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
// TODO: Properly clear the old buckets.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 42ac1a2..d7b7296 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -135,7 +135,7 @@
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 9cbe6f6..0dbdd29 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -153,7 +153,7 @@
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
- mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+ mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
} else if (kv.has_value_int()) {
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
} else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 2344cb4..4660263 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -43,12 +43,12 @@
int& logTrackerIndex) {
auto logTrackerIt = logTrackerMap.find(what);
if (logTrackerIt == logTrackerMap.end()) {
- ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+ ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str());
return false;
}
if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
- ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
- "\"what\" can only about one atom type.",
+ ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+ "the \"what\" can only about one atom type.",
what.c_str());
return false;
}
@@ -67,14 +67,14 @@
unordered_map<int, std::vector<int>>& conditionToMetricMap) {
auto condition_it = conditionTrackerMap.find(condition);
if (condition_it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", condition.c_str());
+ ALOGW("cannot find Condition \"%s\" in the config", condition.c_str());
return false;
}
for (const auto& link : links) {
auto it = conditionTrackerMap.find(link.condition());
if (it == conditionTrackerMap.end()) {
- ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ ALOGW("cannot find Condition \"%s\" in the config", link.condition().c_str());
return false;
}
allConditionTrackers[condition_it->second]->setSliced(true);
@@ -110,7 +110,7 @@
new CombinationLogMatchingTracker(logMatcher.name(), index));
break;
default:
- ALOGE("Matcher %s malformed", logMatcher.name().c_str());
+ ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str());
return false;
// continue;
}
@@ -158,7 +158,7 @@
break;
}
default:
- ALOGE("Condition %s malformed", condition.name().c_str());
+ ALOGE("Condition \"%s\" malformed", condition.name().c_str());
return false;
}
if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
@@ -204,7 +204,7 @@
for (int i = 0; i < config.count_metric_size(); i++) {
const CountMetric& metric = config.count_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in CountMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -341,7 +341,7 @@
for (int i = 0; i < config.value_metric_size(); i++) {
const ValueMetric& metric = config.value_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -387,7 +387,7 @@
for (int i = 0; i < config.gauge_metric_size(); i++) {
const GaugeMetric& metric = config.gauge_metric(i);
if (!metric.has_what()) {
- ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+ ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
return false;
}
@@ -438,7 +438,7 @@
const Alert& alert = config.alert(i);
const auto& itr = metricProducerMap.find(alert.metric_name());
if (itr == metricProducerMap.end()) {
- ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+ ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(),
alert.metric_name().c_str());
return false;
}
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4f5df55..3fbcfee 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -125,7 +125,7 @@
}
message StatsLogReport {
- optional int32 metric_id = 1;
+ optional string metric_name = 1;
optional int64 start_report_nanos = 2;
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index e75a37f..c8fa155 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -88,7 +88,7 @@
UNKNOWN = 0;
FALSE = 1;
}
- optional InitialValue initial_value = 5 [default = UNKNOWN];
+ optional InitialValue initial_value = 5 [default = FALSE];
repeated KeyMatcher dimension = 6;
}
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 80a0068..11fb011 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -70,6 +70,7 @@
simpleCondition.set_start("SCREEN_TURNED_ON");
simpleCondition.set_stop("SCREEN_TURNED_OFF");
simpleCondition.set_count_nesting(false);
+ simpleCondition.set_initial_value(SimpleCondition_InitialValue_UNKNOWN);
unordered_map<string, int> trackerNameIndexMap;
trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk
new file mode 100644
index 0000000..faf2d2c
--- /dev/null
+++ b/cmds/statsd/tools/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
new file mode 100644
index 0000000..1bd5f30
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -0,0 +1,34 @@
+# 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.
+#
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES += ../../src/stats_log.proto \
+ ../../src/atoms_copy.proto
+
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := StatsdDogfood
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_DEX_PREOPT := false
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
new file mode 100644
index 0000000..cd76c9d
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.statsd.dogfood"
+ android:sharedUserId="android.uid.system"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.DUMP" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:launchMode="singleTop" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..55621cc
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..11ec206
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7c02b78
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..915d914
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
new file mode 100644
index 0000000..3997897
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, 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.
+*/
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/push_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_green_light"
+ android:text="@string/push_config"/>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_a_wake_lock_acquire1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_get_wl1"/>
+ <Button android:id="@+id/app_a_wake_lock_release1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_release_wl1"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_a_wake_lock_acquire2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_get_wl2"/>
+ <Button android:id="@+id/app_a_wake_lock_release2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_a_release_wl2"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_b_wake_lock_acquire1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_get_wl1"/>
+ <Button android:id="@+id/app_b_wake_lock_release1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_release_wl1"/>
+ </LinearLayout>
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/app_b_wake_lock_acquire2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_get_wl2"/>
+ <Button android:id="@+id/app_b_wake_lock_release2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/app_b_release_wl2"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/plug"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/plug"/>
+
+ <Button android:id="@+id/unplug"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/unplug"/>
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/screen_on"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screen_on"/>
+
+ <Button android:id="@+id/screen_off"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/screen_off"/>
+ </LinearLayout>
+
+ <Button android:id="@+id/dump"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/holo_purple"
+ android:text="@string/dump"/>
+
+ <TextView
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/report_header"/>
+
+ <TextView
+ android:id="@+id/report_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
new file mode 100644
index 0000000..d5b8fed
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
new file mode 100644
index 0000000..7690df6
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/values/strings.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, 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.
+*/
+-->
+<resources>
+
+ <string name="app_name">Statsd Dogfood</string>
+
+ <string name="statsd_running">Statsd Running</string>
+ <string name="statsd_not_running">Statsd NOT Running</string>
+
+ <string name="push_config">Push baseline config</string>
+
+ <string name="app_a_foreground">App A foreground</string>
+ <string name="app_b_foreground">App B foreground</string>
+
+
+ <string name="app_a_get_wl1">App A get wl_1</string>
+ <string name="app_a_release_wl1">App A release wl_1</string>
+
+ <string name="app_a_get_wl2">App A get wl_2</string>
+ <string name="app_a_release_wl2">App A release wl_2</string>
+
+ <string name="app_b_get_wl1">App B get wl_1</string>
+ <string name="app_b_release_wl1">App B release wl_1</string>
+
+ <string name="app_b_get_wl2">App B get wl_2</string>
+ <string name="app_b_release_wl2">App B release wl_2</string>
+
+ <string name="plug">Plug</string>
+ <string name="unplug">Unplug</string>
+
+ <string name="screen_on">Screen On</string>
+ <string name="screen_off">Screen Off</string>
+
+ <string name="dump">DumpReport</string>
+ <string name="report_header">Report details</string>
+</resources>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
new file mode 100644
index 0000000..f6dea42
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -0,0 +1,245 @@
+/*
+ * 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.statsd.dogfood;
+
+import android.text.format.DateFormat;
+
+import com.android.os.StatsLog;
+
+import java.util.List;
+
+public class DisplayProtoUtils {
+ public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReport report) {
+ sb.append("ConfigKey: ");
+ if (report.hasConfigKey()) {
+ com.android.os.StatsLog.ConfigMetricsReport.ConfigKey key = report.getConfigKey();
+ sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+ .append("\n");
+ }
+
+ sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
+ for (StatsLog.StatsLogReport log : report.getMetricsList()) {
+ sb.append("\n\n");
+ sb.append("metric id: ").append(log.getMetricName()).append("\n");
+ sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
+ sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
+
+ switch (log.getDataCase()) {
+ case DURATION_METRICS:
+ sb.append("Duration metric data\n");
+ displayDurationMetricData(sb, log);
+ break;
+ case EVENT_METRICS:
+ sb.append("Event metric data\n");
+ displayEventMetricData(sb, log);
+ break;
+ case COUNT_METRICS:
+ sb.append("Count metric data\n");
+ displayCountMetricData(sb, log);
+ break;
+ case GAUGE_METRICS:
+ sb.append("Gauge metric data\n");
+ displayGaugeMetricData(sb, log);
+ break;
+ case VALUE_METRICS:
+ sb.append("Value metric data\n");
+ displayValueMetricData(sb, log);
+ break;
+ case DATA_NOT_SET:
+ sb.append("No metric data\n");
+ break;
+ }
+ }
+ }
+
+ public static String getDateStr(long nanoSec) {
+ return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
+ }
+
+ private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
+ for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
+ sb.append(kv.getKey()).append(":");
+ if (kv.hasValueBool()) {
+ sb.append(kv.getValueBool());
+ } else if (kv.hasValueFloat()) {
+ sb.append(kv.getValueFloat());
+ } else if (kv.hasValueInt()) {
+ sb.append(kv.getValueInt());
+ } else if (kv.hasValueStr()) {
+ sb.append(kv.getValueStr());
+ }
+ sb.append(" ");
+ }
+ }
+
+ public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper
+ = log.getDurationMetrics();
+ sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
+ for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
+ sb.append("dimension: ");
+ displayDimension(sb, duration.getDimensionList());
+ sb.append("\n");
+
+ for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList()) {
+ sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+ .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+ .append(info.getDurationNanos()).append(" ns\n");
+ }
+ }
+ }
+
+ public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n");
+ StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper =
+ log.getEventMetrics();
+ for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) {
+ sb.append(getDateStr(event.getTimestampNanos())).append(": ");
+ switch (event.getAtom().getPushedCase()) {
+ case SETTING_CHANGED:
+ sb.append("SETTING_CHANGED\n");
+ break;
+ case SYNC_STATE_CHANGED:
+ sb.append("SYNC_STATE_CHANGED\n");
+ break;
+ case AUDIO_STATE_CHANGED:
+ sb.append("AUDIO_STATE_CHANGED\n");
+ break;
+ case CAMERA_STATE_CHANGED:
+ sb.append("CAMERA_STATE_CHANGED\n");
+ break;
+ case ISOLATED_UID_CHANGED:
+ sb.append("ISOLATED_UID_CHANGED\n");
+ break;
+ case SCREEN_STATE_CHANGED:
+ sb.append("SCREEN_STATE_CHANGED\n");
+ break;
+ case SENSOR_STATE_CHANGED:
+ sb.append("SENSOR_STATE_CHANGED\n");
+ break;
+ case BATTERY_LEVEL_CHANGED:
+ sb.append("BATTERY_LEVEL_CHANGED\n");
+ break;
+ case PLUGGED_STATE_CHANGED:
+ sb.append("PLUGGED_STATE_CHANGED\n");
+ break;
+ case WAKEUP_ALARM_OCCURRED:
+ sb.append("WAKEUP_ALARM_OCCURRED\n");
+ break;
+ case BLE_SCAN_STATE_CHANGED:
+ sb.append("BLE_SCAN_STATE_CHANGED\n");
+ break;
+ case CHARGING_STATE_CHANGED:
+ sb.append("CHARGING_STATE_CHANGED\n");
+ break;
+ case GPS_SCAN_STATE_CHANGED:
+ sb.append("GPS_SCAN_STATE_CHANGED\n");
+ break;
+ case KERNEL_WAKEUP_REPORTED:
+ sb.append("KERNEL_WAKEUP_REPORTED\n");
+ break;
+ case WAKELOCK_STATE_CHANGED:
+ sb.append("WAKELOCK_STATE_CHANGED\n");
+ break;
+ case WIFI_LOCK_STATE_CHANGED:
+ sb.append("WIFI_LOCK_STATE_CHANGED\n");
+ break;
+ case WIFI_SCAN_STATE_CHANGED:
+ sb.append("WIFI_SCAN_STATE_CHANGED\n");
+ break;
+ case BLE_SCAN_RESULT_RECEIVED:
+ sb.append("BLE_SCAN_RESULT_RECEIVED\n");
+ break;
+ case DEVICE_ON_STATUS_CHANGED:
+ sb.append("DEVICE_ON_STATUS_CHANGED\n");
+ break;
+ case FLASHLIGHT_STATE_CHANGED:
+ sb.append("FLASHLIGHT_STATE_CHANGED\n");
+ break;
+ case SCREEN_BRIGHTNESS_CHANGED:
+ sb.append("SCREEN_BRIGHTNESS_CHANGED\n");
+ break;
+ case UID_PROCESS_STATE_CHANGED:
+ sb.append("UID_PROCESS_STATE_CHANGED\n");
+ break;
+ case UID_WAKELOCK_STATE_CHANGED:
+ sb.append("UID_WAKELOCK_STATE_CHANGED\n");
+ break;
+ case DEVICE_TEMPERATURE_REPORTED:
+ sb.append("DEVICE_TEMPERATURE_REPORTED\n");
+ break;
+ case SCHEDULED_JOB_STATE_CHANGED:
+ sb.append("SCHEDULED_JOB_STATE_CHANGED\n");
+ break;
+ case MEDIA_CODEC_ACTIVITY_CHANGED:
+ sb.append("MEDIA_CODEC_ACTIVITY_CHANGED\n");
+ break;
+ case WIFI_SIGNAL_STRENGTH_CHANGED:
+ sb.append("WIFI_SIGNAL_STRENGTH_CHANGED\n");
+ break;
+ case PHONE_SIGNAL_STRENGTH_CHANGED:
+ sb.append("PHONE_SIGNAL_STRENGTH_CHANGED\n");
+ break;
+ case DEVICE_IDLE_MODE_STATE_CHANGED:
+ sb.append("DEVICE_IDLE_MODE_STATE_CHANGED\n");
+ break;
+ case BATTERY_SAVER_MODE_STATE_CHANGED:
+ sb.append("BATTERY_SAVER_MODE_STATE_CHANGED\n");
+ break;
+ case PROCESS_LIFE_CYCLE_STATE_CHANGED:
+ sb.append("PROCESS_LIFE_CYCLE_STATE_CHANGED\n");
+ break;
+ case ACTIVITY_FOREGROUND_STATE_CHANGED:
+ sb.append("ACTIVITY_FOREGROUND_STATE_CHANGED\n");
+ break;
+ case BLE_UNOPTIMIZED_SCAN_STATE_CHANGED:
+ sb.append("BLE_UNOPTIMIZED_SCAN_STATE_CHANGED\n");
+ break;
+ case LONG_PARTIAL_WAKELOCK_STATE_CHANGED:
+ sb.append("LONG_PARTIAL_WAKELOCK_STATE_CHANGED\n");
+ break;
+ case PUSHED_NOT_SET:
+ sb.append("PUSHED_NOT_SET\n");
+ break;
+ }
+ }
+ }
+
+ public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper
+ = log.getCountMetrics();
+ sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
+ for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
+ sb.append("dimension: ");
+ displayDimension(sb, count.getDimensionList());
+ sb.append("\n");
+
+ for (StatsLog.CountBucketInfo info : count.getBucketInfoList()) {
+ sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+ .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+ .append(info.getCount()).append("\n");
+ }
+ }
+ }
+
+ public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Display me!");
+ }
+
+ public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+ sb.append("Display me!");
+ }
+}
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
new file mode 100644
index 0000000..5e3160e
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -0,0 +1,259 @@
+/*
+ * 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.statsd.dogfood;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.StatsLog;
+import android.util.StatsManager;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.os.IStatsManager;
+import android.os.ServiceManager;
+
+import java.io.InputStream;
+
+import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport;
+
+public class MainActivity extends Activity {
+ private final static String TAG = "StatsdDogfood";
+
+ final int[] mUids = {11111111, 2222222};
+ StatsManager mStatsManager;
+ TextView mReportText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(0, "wl_2");
+ }
+ });
+
+ findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockAcquire(1, "wl_2");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_release1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_1");
+ }
+ });
+
+
+ findViewById(R.id.app_b_wake_lock_release1).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_1");
+ }
+ });
+
+ findViewById(R.id.app_a_wake_lock_release2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(0, "wl_2");
+ }
+ });
+
+
+ findViewById(R.id.app_b_wake_lock_release2).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onWakeLockRelease(1, "wl_2");
+ }
+ });
+
+
+ findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1);
+ }
+ });
+
+ findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0);
+ }
+ });
+
+ findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2);
+ }
+ });
+
+ findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1);
+ }
+ });
+
+ mReportText = (TextView) findViewById(R.id.report_text);
+
+ findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!statsdRunning()) {
+ return;
+ }
+ if (mStatsManager != null) {
+ byte[] data = mStatsManager.getData("fake");
+ if (data != null) {
+ displayData(data);
+ } else {
+ mReportText.setText("Failed!");
+ }
+ }
+ }
+ });
+
+ findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ try {
+ if (!statsdRunning()) {
+ return;
+ }
+ Resources res = getResources();
+ InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config);
+
+ byte[] config = new byte[inputStream.available()];
+ inputStream.read(config);
+ if (mStatsManager != null) {
+ if (mStatsManager.addConfiguration("fake",
+ config, getPackageName(), MainActivity.this.getClass().getName())) {
+ Toast.makeText(
+ MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(MainActivity.this, "Config push FAILED!",
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ } catch (Exception e) {
+ Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG);
+ }
+ }
+ });
+ mStatsManager = (StatsManager) getSystemService("stats");
+ }
+
+ private boolean statsdRunning() {
+ if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) {
+ Log.d(TAG, "Statsd not running");
+ Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0));
+ int pkg = intent.getIntExtra("pkg", 0);
+ String name = intent.getStringExtra("name");
+ if (intent.hasExtra("acquire")) {
+ onWakeLockAcquire(pkg, name);
+ } else if (intent.hasExtra("release")) {
+ onWakeLockRelease(pkg, name);
+ }
+ }
+
+ private void displayData(byte[] data) {
+ com.android.os.StatsLog.ConfigMetricsReport report = null;
+ boolean good = false;
+ if (data != null) {
+ try {
+ report = com.android.os.StatsLog.ConfigMetricsReport.parseFrom(data);
+ good = true;
+ } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+ // display it in the text view.
+ }
+ }
+ int size = data == null ? 0 : data.length;
+ StringBuilder sb = new StringBuilder();
+ sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!");
+ sb.append(" size:").append(size).append("\n");
+
+ if (good && report != null) {
+ displayLogReport(sb, report);
+ mReportText.setText(sb.toString());
+ }
+ }
+
+
+ private void onWakeLockAcquire(int id, String name) {
+ if (id > 1) {
+ Log.d(TAG, "invalid pkg id");
+ return;
+ }
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1);
+ StringBuilder sb = new StringBuilder();
+ sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+ .append(", ").append(name).append(", 1);");
+ Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+ }
+
+ private void onWakeLockRelease(int id, String name) {
+ if (id > 1) {
+ Log.d(TAG, "invalid pkg id");
+ return;
+ }
+ StatsLog.write(10, mUids[id], 0, name, 0);
+ StringBuilder sb = new StringBuilder();
+ sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+ .append(", ").append(name).append(", 0);");
+ Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index fb201ef..8684d0a 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -5187,7 +5187,7 @@
com.android.ims.internal.IImsService
com.android.ims.internal.IImsService$Stub
com.android.ims.internal.IImsServiceController
-com.android.ims.internal.IImsServiceFeatureListener
+com.android.ims.internal.IImsServiceFeatureCallback
com.android.ims.internal.IImsUt
com.android.ims.internal.IImsUt$Stub
com.android.ims.internal.IImsUtListener
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 1503445..d8c7929 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,7 +3,26 @@
srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
}
-filegroup {
- name: "IKeystoreService.aidl",
+// only used by key_store_service
+cc_library_static {
+ name: "libkeystore_aidl",
srcs: ["android/security/IKeystoreService.aidl"],
+ aidl: {
+ export_aidl_headers: true,
+ include_dirs: ["frameworks/base/core/java/"],
+ },
+ header_libs: [
+ "libkeystore_headers",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libhardware",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libselinux",
+ "libutils",
+ ],
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a558d68..8824643 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -358,6 +358,11 @@
*/
public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+ /**
+ * Action to lock the screen
+ */
+ public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 064e978..02b7f8c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -519,11 +519,15 @@
* process that contains activities. */
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
+ /** @hide Process is being cached for later use and has an activity that corresponds
+ * to an existing recent task. */
+ public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 18;
/** @hide Process does not exist. */
- public static final int PROCESS_STATE_NONEXISTENT = 18;
+ public static final int PROCESS_STATE_NONEXISTENT = 19;
// NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
// to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9e926bd..d1aacad 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -72,6 +72,7 @@
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getDeletedChannelCount(String pkg, int uid);
void deleteNotificationChannelGroup(String pkg, String channelGroupId);
+ NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
ParceledListSlice getNotificationChannelGroups(String pkg);
boolean onlyHasDefaultChannel(String pkg, int uid);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f931589..659cf16 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,58 @@
private static boolean localLOGV = false;
/**
+ * Intent that is broadcast when a {@link NotificationChannel} is blocked
+ * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
+ * (when {@link NotificationChannel#getImportance()} is anything other than
+ * {@link #IMPORTANCE_NONE}).
+ *
+ * This broadcast is only sent to the app that owns the channel that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
+ * object which has a new blocked state.
+ *
+ * The value will be the {@link NotificationChannel#getId()} of the channel for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
+ * the {@link NotificationChannelGroup#getId()} of the group for
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+ */
+ public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
+ "android.app.extra.BLOCK_STATE_CHANGED_ID";
+
+ /**
+ * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+ * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked
+ * state as a boolean.
+ *
+ * The value will be {@code true} if this channel or group is now blocked and {@code false} if
+ * this channel or group is now unblocked.
+ */
+ public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+
+
+ /**
+ * Intent that is broadcast when a {@link NotificationChannelGroup} is
+ * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
+ *
+ * This broadcast is only sent to the app that owns the channel group that has changed.
+ *
+ * Input: nothing
+ * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
+ "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
+
+ /**
* Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
* This broadcast is only sent to registered receivers.
*
@@ -504,6 +556,20 @@
}
/**
+ * Returns the notification channel group settings for a given channel group id.
+ *
+ * The channel group must belong to your package, or null will be returned.
+ */
+ public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
+ INotificationManager service = getService();
+ try {
+ return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channel groups belonging to the calling app.
*/
public List<NotificationChannelGroup> getNotificationChannelGroups() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f0226b7..0bca969 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3858,6 +3858,47 @@
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
+ return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+ }
+
+ /**
+ * Called by a device or profile owner, or delegated certificate installer, to install a
+ * certificate chain and corresponding private key for the leaf certificate. All apps within the
+ * profile will be able to access the certificate chain and use the private key, given direct
+ * user approval (if the user is allowed to select the private key).
+ *
+ * <p>The caller of this API may grant itself access to the certificate and private key
+ * immediately, without user approval. It is a best practice not to request this unless strictly
+ * necessary since it opens up additional security vulnerabilities.
+ *
+ * <p>Whether this key is offered to the user for approval at all or not depends on the
+ * {@code isUserSelectable} parameter.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if calling from a delegated certificate installer.
+ * @param privKey The private key to install.
+ * @param certs The certificate chain to install. The chain should start with the leaf
+ * certificate and include the chain of trust in order. This will be returned by
+ * {@link android.security.KeyChain#getCertificateChain}.
+ * @param alias The private key alias under which to install the certificate. If a certificate
+ * with that alias already exists, it will be overwritten.
+ * @param requestAccess {@code true} to request that the calling app be granted access to the
+ * credentials immediately. Otherwise, access to the credentials will be gated by user
+ * approval.
+ * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+ * Certificate Selection prompt, false to indicate that this key can only be granted
+ * access by implementing
+ * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+ * @return {@code true} if the keys were installed, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+ * owner.
+ * @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
+ */
+ public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
+ @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
+ boolean isUserSelectable) {
throwIfParentInstance("installKeyPair");
try {
final byte[] pemCert = Credentials.convertToPem(certs[0]);
@@ -3868,7 +3909,7 @@
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
- pemChain, alias, requestAccess);
+ pemChain, alias, requestAccess, isUserSelectable);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index be0b920..b7740e9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -162,7 +162,8 @@
boolean isCaCertApproved(in String alias, int userHandle);
boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
- in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+ in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
+ boolean isUserSelectable);
boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 530d84b..7c40b4e 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@
public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
/**
+ * Allows this job to run despite doze restrictions as long as the app is in the foreground
+ * or on the temporary whitelist
+ * @hide
+ */
+ public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -1333,6 +1340,30 @@
}
/**
+ * Setting this to true indicates that this job is important while the scheduling app
+ * is in the foreground or on the temporary whitelist for background restrictions.
+ * This means that the system will relax doze restrictions on this job during this time.
+ *
+ * Apps should use this flag only for short jobs that are essential for the app to function
+ * properly in the foreground.
+ *
+ * Note that once the scheduling app is no longer whitelisted from background restrictions
+ * and in the background, or the job failed due to unsatisfied constraints,
+ * this job should be expected to behave like other jobs without this flag.
+ *
+ * @param importantWhileForeground whether to relax doze restrictions for this job when the
+ * app is in the foreground. False by default.
+ */
+ public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+ if (importantWhileForeground) {
+ mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
+ } else {
+ mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
+ }
+ return this;
+ }
+
+ /**
* Set whether or not to persist this job across device reboots.
*
* @param isPersisted True to indicate that the job will be written to
@@ -1395,6 +1426,10 @@
"persisted job");
}
}
+ if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
+ throw new IllegalArgumentException("An important while foreground job cannot "
+ + "have a time delay");
+ }
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 33386e5..7f24c51 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -145,7 +145,7 @@
}
/**
- * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+ * Starts an asynchronous load of the data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2034280..edb27cd 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@
import static android.os.Build.VERSION_CODES.DONUT;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
@@ -890,6 +891,29 @@
public int versionCode;
/**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
+ /**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
*/
@@ -1305,6 +1329,8 @@
dest.writeInt(targetSandboxVersion);
dest.writeString(classLoaderName);
dest.writeStringArray(splitClassLoaderNames);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1372,6 +1398,8 @@
targetSandboxVersion = source.readInt();
classLoaderName = source.readString();
splitClassLoaderNames = source.readStringArray();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
}
/**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ba488f6..f8889b6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -289,6 +290,29 @@
/** @hide */
public boolean isStaticOverlay;
+ /**
+ * The user-visible SDK version (ex. 26) of the framework against which the application claims
+ * to have been compiled, or {@code 0} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ public int compileSdkVersion;
+
+ /**
+ * The development codename (ex. "O", "REL") of the framework against which the application
+ * claims to have been compiled, or {@code null} if not specified.
+ * <p>
+ * This property is the compile-time equivalent of
+ * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+ *
+ * @hide For platform use only; we don't expect developers to need to read this value.
+ */
+ @Nullable
+ public String compileSdkVersionCodename;
+
public PackageInfo() {
}
@@ -344,6 +368,8 @@
dest.writeString(overlayTarget);
dest.writeInt(isStaticOverlay ? 1 : 0);
dest.writeInt(overlayPriority);
+ dest.writeInt(compileSdkVersion);
+ dest.writeString(compileSdkVersionCodename);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -396,6 +422,8 @@
overlayTarget = source.readString();
isStaticOverlay = source.readInt() != 0;
overlayPriority = source.readInt();
+ compileSdkVersion = source.readInt();
+ compileSdkVersionCodename = source.readString();
// The component lists were flattened with the redundant ApplicationInfo
// instances omitted. Distribute the canonical one here as appropriate.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c5cf15..d9ac681 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -678,6 +678,8 @@
pi.overlayTarget = p.mOverlayTarget;
pi.overlayPriority = p.mOverlayPriority;
pi.isStaticOverlay = p.mIsStaticOverlay;
+ pi.compileSdkVersion = p.mCompileSdkVersion;
+ pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -2076,6 +2078,16 @@
pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
+ pkg.mCompileSdkVersion = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
+ pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
+ pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
+ if (pkg.mCompileSdkVersionCodename != null) {
+ pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
+ }
+ pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
+
sa.recycle();
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
@@ -5967,6 +5979,9 @@
public boolean mIsStaticOverlay;
public boolean mTrustedOverlay;
+ public int mCompileSdkVersion;
+ public String mCompileSdkVersionCodename;
+
/**
* Data used to feed the KeySetManagerService
*/
@@ -6458,6 +6473,8 @@
mOverlayPriority = dest.readInt();
mIsStaticOverlay = (dest.readInt() == 1);
mTrustedOverlay = (dest.readInt() == 1);
+ mCompileSdkVersion = dest.readInt();
+ mCompileSdkVersionCodename = dest.readString();
mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6581,6 +6598,8 @@
dest.writeInt(mOverlayPriority);
dest.writeInt(mIsStaticOverlay ? 1 : 0);
dest.writeInt(mTrustedOverlay ? 1 : 0);
+ dest.writeInt(mCompileSdkVersion);
+ dest.writeString(mCompileSdkVersionCodename);
dest.writeArraySet(mSigningKeys);
dest.writeArraySet(mUpgradeKeySets);
writeKeySetMapping(dest, mKeySetMapping);
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 8b0fef4..5adb119 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -570,6 +570,16 @@
mAvailableNonPrimaryConnections.clear();
}
+ /**
+ * Close non-primary connections that are not currently in use. This method is safe to use
+ * in finalize block as it doesn't throw RuntimeExceptions.
+ */
+ void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
+ synchronized (mLock) {
+ closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+ }
+ }
+
// Can't throw.
private void closeExcessConnectionsAndLogExceptionsLocked() {
int availableCount = mAvailableNonPrimaryConnections.size();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 863fb19..09bb9c6 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1740,7 +1740,8 @@
private int executeSql(String sql, Object[] bindArgs) throws SQLException {
acquireReference();
try {
- if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+ final int statementType = DatabaseUtils.getSqlStatementType(sql);
+ if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
boolean disableWal = false;
synchronized (mLock) {
if (!mHasAttachedDbsLocked) {
@@ -1754,11 +1755,14 @@
}
}
- SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
- try {
+ try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
return statement.executeUpdateDelete();
} finally {
- statement.close();
+ // If schema was updated, close non-primary connections, otherwise they might
+ // have outdated schema information
+ if (statementType == DatabaseUtils.STATEMENT_DDL) {
+ mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+ }
}
} finally {
releaseReference();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e845359..cd551bd 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@
public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
/**
+ * Persist brightness slider events.
+ */
+ public abstract void persistBrightnessSliderEvents();
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 025d46d..151e62d 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -34,7 +34,7 @@
/* Returns a file descriptor for communicating with the USB device.
* The native fd can be passed to usb_device_new() in libusbhost.
*/
- ParcelFileDescriptor openDevice(String deviceName);
+ ParcelFileDescriptor openDevice(String deviceName, String packageName);
/* Returns the currently attached USB accessory */
UsbAccessory getCurrentAccessory();
@@ -55,7 +55,7 @@
void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId);
/* Returns true if the caller has permission to access the device. */
- boolean hasDevicePermission(in UsbDevice device);
+ boolean hasDevicePermission(in UsbDevice device, String packageName);
/* Returns true if the caller has permission to access the accessory. */
boolean hasAccessoryPermission(in UsbAccessory accessory);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 6ce9669..bdb90bc 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -344,7 +344,7 @@
public UsbDeviceConnection openDevice(UsbDevice device) {
try {
String deviceName = device.getDeviceName();
- ParcelFileDescriptor pfd = mService.openDevice(deviceName);
+ ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName());
if (pfd != null) {
UsbDeviceConnection connection = new UsbDeviceConnection(device);
boolean result = connection.open(deviceName, pfd, mContext);
@@ -400,6 +400,9 @@
* Permission might have been granted temporarily via
* {@link #requestPermission(UsbDevice, PendingIntent)} or
* by the user choosing the caller as the default application for the device.
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
*
* @param device to check permissions for
* @return true if caller has permission
@@ -409,7 +412,7 @@
return false;
}
try {
- return mService.hasDevicePermission(device);
+ return mService.hasDevicePermission(device, mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -450,6 +453,10 @@
* permission was granted by the user
* </ul>
*
+ * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+ * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+ * have additionally the {@link android.Manifest.permission#CAMERA} permission.
+ *
* @param device to request permissions for
* @param pi PendingIntent for returning result
*/
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 64f8f39..d6e62cf 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.annotation.NonNull;
import android.annotation.StringDef;
import android.os.Build;
import android.os.Parcel;
@@ -27,8 +28,10 @@
import java.util.Arrays;
/**
- * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
- * RFC 4301.
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecAlgorithm implements Parcelable {
/**
@@ -39,16 +42,16 @@
public static final String CRYPT_AES_CBC = "cbc(aes)";
/**
- * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
- * applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
*/
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
/**
- * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
- * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+ * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
*
* <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
*/
@@ -69,7 +72,7 @@
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
/**
- * SHA512 HMAC Authentication/Integrity Algorithm
+ * SHA512 HMAC Authentication/Integrity Algorithm.
*
* <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
*/
@@ -80,9 +83,9 @@
*
* <p>Valid lengths for keying material are {160, 224, 288}.
*
- * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
- * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
- * invocation with the same key.
+ * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+ * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+ * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
*
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
*/
@@ -105,48 +108,47 @@
private final int mTruncLenBits;
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
*
- * @param algorithm type for IpSec.
- * @param key non-null Key padded to a multiple of 8 bits.
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
*/
- public IpSecAlgorithm(String algorithm, byte[] key) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) {
this(algorithm, key, key.length * 8);
}
/**
- * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
- * algorithm
+ * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+ * defined as constants in this class.
*
- * @param algoName precise name of the algorithm to be used.
- * @param key non-null Key padded to a multiple of 8 bits.
- * @param truncLenBits the number of bits of output hash to use; only meaningful for
- * Authentication or Authenticated Encryption (equivalent to ICV length).
+ * <p>This constructor only supports algorithms that use a truncation length. i.e.
+ * Authentication and Authenticated Encryption algorithms.
+ *
+ * @param algorithm name of the algorithm.
+ * @param key key padded to a multiple of 8 bits.
+ * @param truncLenBits number of bits of output hash to use.
*/
- public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
- if (!isTruncationLengthValid(algoName, truncLenBits)) {
+ public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+ if (!isTruncationLengthValid(algorithm, truncLenBits)) {
throw new IllegalArgumentException("Unknown algorithm or invalid length");
}
- mName = algoName;
+ mName = algorithm;
mKey = key.clone();
mTruncLenBits = Math.min(truncLenBits, key.length * 8);
}
- /** Retrieve the algorithm name */
+ /** Get the algorithm name */
public String getName() {
return mName;
}
- /** Retrieve the key for this algorithm */
+ /** Get the key for this algorithm */
public byte[] getKey() {
return mKey.clone();
}
- /**
- * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
- * the length in bits of the key.
- */
+ /** Get the truncation length of this algorithm, in bits */
public int getTruncationLengthBits() {
return mTruncLenBits;
}
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 61b13a9..e6cd3fc 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -20,7 +20,12 @@
import com.android.internal.annotations.VisibleForTesting;
-/** @hide */
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
public final class IpSecConfig implements Parcelable {
private static final String TAG = "IpSecConfig";
@@ -38,6 +43,9 @@
// for outbound packets. It may also be used to select packets.
private Network mNetwork;
+ /**
+ * This class captures the parameters that specifically apply to inbound or outbound traffic.
+ */
public static class Flow {
// Minimum requirements for identifying a transform
// SPI identifying the IPsec flow in packet processing
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index eccd5f4..a9e60ec 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -37,22 +38,28 @@
import java.net.Socket;
/**
- * This class contains methods for managing IPsec sessions, which will perform kernel-space
- * encryption and decryption of socket or Network traffic.
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
*
- * <p>An IpSecManager may be obtained by calling {@link
- * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
- * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create a VPN should use {@link VpnService}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
@SystemService(Context.IPSEC_SERVICE)
public final class IpSecManager {
private static final String TAG = "IpSecManager";
/**
- * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
*
* <p>No IPsec packet may contain an SPI of 0.
+ *
+ * @hide
*/
+ @TestApi
public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
/** @hide */
@@ -66,10 +73,12 @@
public static final int INVALID_RESOURCE_ID = 0;
/**
- * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
- * request. If encountered, selection of a new SPI is required before a transform may be
- * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
- * or reserved using reserveSecurityParameterIndex.
+ * Thrown to indicate that a requested SPI is in use.
+ *
+ * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+ * one device. If this error is encountered, a new SPI is required before a transform may be
+ * created. This error can be avoided by calling {@link
+ * IpSecManager#reserveSecurityParameterIndex}.
*/
public static final class SpiUnavailableException extends AndroidException {
private final int mSpi;
@@ -78,24 +87,26 @@
* Construct an exception indicating that a transform with the given SPI is already in use
* or otherwise unavailable.
*
- * @param msg Description indicating the colliding SPI
+ * @param msg description indicating the colliding SPI
* @param spi the SPI that could not be used due to a collision
*/
SpiUnavailableException(String msg, int spi) {
- super(msg + "(spi: " + spi + ")");
+ super(msg + " (spi: " + spi + ")");
mSpi = spi;
}
- /** Retrieve the SPI that caused a collision */
+ /** Get the SPI that caused a collision. */
public int getSpi() {
return mSpi;
}
}
/**
- * Indicates that the requested system resource for IPsec, such as a socket or other system
- * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
- * type requested.
+ * Thrown to indicate that an IPsec resource is unavailable.
+ *
+ * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+ * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+ * allocated objects of the type requested.
*/
public static final class ResourceUnavailableException extends AndroidException {
@@ -106,6 +117,13 @@
private final IIpSecService mService;
+ /**
+ * This class represents a reserved SPI.
+ *
+ * <p>Objects of this type are used to track reserved security parameter indices. They can be
+ * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released
+ * by calling {@link #close()} when they are no longer needed.
+ */
public static final class SecurityParameterIndex implements AutoCloseable {
private final IIpSecService mService;
private final InetAddress mRemoteAddress;
@@ -113,7 +131,7 @@
private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
private int mResourceId;
- /** Return the underlying SPI held by this object */
+ /** Get the underlying SPI held by this object. */
public int getSpi() {
return mSpi;
}
@@ -135,6 +153,7 @@
mCloseGuard.close();
}
+ /** Check that the SPI was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -197,13 +216,13 @@
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve a random SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
@@ -223,17 +242,18 @@
}
/**
- * Reserve an SPI for traffic bound towards the specified remote address.
+ * Reserve the requested SPI for traffic bound to or from the specified remote address.
*
* <p>If successful, this SPI is guaranteed available until released by a call to {@link
* SecurityParameterIndex#close()}.
*
* @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
- * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
- * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+ * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+ * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
* @return the reserved SecurityParameterIndex
* @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
* for this user
+ * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
*/
public SecurityParameterIndex reserveSecurityParameterIndex(
int direction, InetAddress remoteAddress, int requestedSpi)
@@ -245,16 +265,28 @@
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(Socket, IpSecTransform)};
+ * Apply an IPsec transform to a stream socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a stream socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -265,16 +297,28 @@
}
/**
- * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
+ * Apply an IPsec transform to a datagram socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a datagram socket
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
* @hide
*/
public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -285,16 +329,28 @@
}
/**
- * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
- * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
- * transform. For security reasons, attempts to send traffic to any IP address other than the
- * address associated with that transform will throw an IOException. In addition, if the
- * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
- * send() or receive() until the transform is removed from the socket by calling {@link
- * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
+ * Apply an IPsec transform to a socket.
+ *
+ * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+ * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+ * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+ * unprotected traffic can resume on that socket.
+ *
+ * <p>For security reasons, the destination address of any traffic on the socket must match the
+ * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+ * other IP address will result in an IOException. In addition, reads and writes on the socket
+ * will throw IOException if the user deactivates the transform (by calling {@link
+ * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+ *
+ * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+ * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+ * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+ * allows rekey procedures where both transforms are valid until both endpoints are using the
+ * new transform and all in-flight packets have been received.
*
* @param socket a socket file descriptor
- * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ * @param transform a transport mode {@code IpSecTransform}
+ * @throws IOException indicating that the transform could not be applied
*/
public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -323,6 +379,7 @@
* Applications should probably not use this API directly. Instead, they should use {@link
* VpnService} to provide VPN capability in a more generic fashion.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a {@link Network} that will be tunneled via IP Sec.
* @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
* @hide
@@ -330,14 +387,19 @@
public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a stream socket.
*
- * @param socket a socket that previously had a transform applied to it.
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -348,14 +410,19 @@
}
/**
- * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a datagram socket.
*
- * @param socket a socket that previously had a transform applied to it.
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
* @hide
*/
public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -366,14 +433,19 @@
}
/**
- * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
- * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
- * communication in the clear in the event socket reuse is desired. This operation will succeed
- * regardless of the underlying state of a transform. If a transform is removed, communication
- * on all sockets to which that transform was applied will fail until this method is called.
+ * Remove an IPsec transform from a socket.
*
- * @param socket a socket file descriptor that previously had a transform applied to it.
+ * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+ * regardless of the state of the transform. Removing a transform from a socket allows the
+ * socket to be reused for communication in the clear.
+ *
+ * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+ * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+ * is called.
+ *
+ * @param socket a socket that previously had a transform applied to it
* @param transform the IPsec Transform that was previously applied to the given socket
+ * @throws IOException indicating that the transform could not be removed from the socket
*/
public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
throws IOException {
@@ -382,7 +454,7 @@
}
}
- /* Call down to activate a transform */
+ /* Call down to remove a transform */
private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
try {
mService.removeTransportModeTransform(pfd, transform.getResourceId());
@@ -397,6 +469,7 @@
* all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
* lost, all traffic will drop.
*
+ * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
* @param net a network that currently has transform applied to it.
* @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
* network
@@ -405,11 +478,18 @@
public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
/**
- * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
- * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+ * This class provides access to a UDP encapsulation Socket.
*
- * <p>The socket provided by this class cannot be re-bound or closed via the inner
- * FileDescriptor. Instead, disposing of this socket requires a call to close().
+ * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+ * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+ * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+ * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+ * #getSocket}, but should use {@link #close} instead.
+ *
+ * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+ * of the next user who binds to that port. To prevent this scenario, these sockets are held
+ * open by the system so that they may only be closed by calling {@link #close} or when the user
+ * process exits.
*/
public static final class UdpEncapsulationSocket implements AutoCloseable {
private final ParcelFileDescriptor mPfd;
@@ -443,7 +523,7 @@
mCloseGuard.open("constructor");
}
- /** Access the inner UDP Encapsulation Socket */
+ /** Get the wrapped socket. */
public FileDescriptor getSocket() {
if (mPfd == null) {
return null;
@@ -451,22 +531,19 @@
return mPfd.getFileDescriptor();
}
- /** Retrieve the port number of the inner encapsulation socket */
+ /** Get the bound port of the wrapped socket. */
public int getPort() {
return mPort;
}
- @Override
/**
- * Release the resources that have been reserved for this Socket.
+ * Close this socket.
*
- * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
- * system. This must be done as part of cleanup following use of a socket. Failure to do so
- * will cause the socket to count against a total allocation limit for IpSec and eventually
- * fail due to resource limits.
- *
- * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+ * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+ * resource limits, and forgetting to close them eventually will result in {@link
+ * ResourceUnavailableException} being thrown.
*/
+ @Override
public void close() throws IOException {
try {
mService.closeUdpEncapsulationSocket(mResourceId);
@@ -483,6 +560,7 @@
mCloseGuard.close();
}
+ /** Check that the socket was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -499,21 +577,14 @@
};
/**
- * Open a socket that is bound to a free UDP port on the system.
+ * Open a socket for UDP encapsulation and bind to the given port.
*
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
- *
- * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
- * method will bind to the specified port or fail. To retrieve the port number, call {@link
- * android.system.Os#getsockname(FileDescriptor)}.
- * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
- * of the object.
+ * @param port a local UDP port
+ * @return a socket that is bound to the given port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -533,17 +604,16 @@
}
/**
- * Open a socket that is bound to a port selected by the system.
+ * Open a socket for UDP encapsulation.
*
- * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
- * the caller. This provides safe access to a socket on a port that can later be used as a UDP
- * Encapsulation port.
+ * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
*
- * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
- * socket port. Explicitly opening this port is only necessary if communication is desired on
- * that port.
+ * <p>The local port of the returned socket can be obtained by calling {@link
+ * UdpEncapsulationSocket#getPort()}.
*
- * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+ * @return a socket that is bound to a local port
+ * @throws IOException indicating that the socket could not be opened or bound
+ * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
*/
// Returning a socket in this fashion that has been created and bound by the system
// is the only safe way to ensure that a socket is both accessible to the user and
@@ -556,7 +626,7 @@
}
/**
- * Retrieve an instance of an IpSecManager within you application context
+ * Construct an instance of IpSecManager within an application context.
*
* @param context the application context for this manager
* @hide
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 48b5bd5..cda4ec7 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,27 +38,29 @@
import java.net.InetAddress;
/**
- * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ * This class represents an IPsec transform, which comprises security associations in one or both
+ * directions.
*
- * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
- * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
- * transform may be disabled automatically, with likely undesirable results.
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an inbound and outbound IPsec security
+ * association. That includes, but is not limited to, algorithm choice, key material, and allocated
+ * system resources.
*
- * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
- * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic towards the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic towards the host.
*/
public static final int DIRECTION_IN = 0;
/**
- * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
- * to traffic from the host.
+ * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+ * applies to traffic from the host.
*/
public static final int DIRECTION_OUT = 1;
@@ -77,16 +79,16 @@
public static final int ENCAP_NONE = 0;
/**
- * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
- * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+ * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+ * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
*
* @hide
*/
public static final int ENCAP_ESPINUDP_NON_IKE = 1;
/**
- * IpSec traffic will be encapsulated within UDP as per <a
- * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+ * IPsec traffic will be encapsulated within UDP as per
+ * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
*
* @hide
*/
@@ -165,13 +167,14 @@
}
/**
- * Deactivate an IpSecTransform and free all resources for that transform that are managed by
- * the system for this Transform.
+ * Deactivate this {@code IpSecTransform} and free allocated resources.
*
- * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
- * refusing to send or receive data. This method will silently succeed if the specified
- * transform has already been removed; thus, it is always safe to attempt cleanup when a
- * transform is no longer needed.
+ * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+ * that socket. Make sure to remove transforms by calling {@link
+ * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+ * socket will not deactivate it (because one transform may be applied to multiple sockets).
+ *
+ * <p>It is safe to call this method on a transform that has already been deactivated.
*/
public void close() {
Log.d(TAG, "Removing Transform with Id " + mResourceId);
@@ -197,6 +200,7 @@
}
}
+ /** Check that the transform was closed properly. */
@Override
protected void finalize() throws Throwable {
if (mCloseGuard != null) {
@@ -264,65 +268,63 @@
}
/**
- * Builder object to facilitate the creation of IpSecTransform objects.
- *
- * <p>Apply additional properties to the transform and then call a build() method to return an
- * IpSecTransform object.
- *
- * @see Builder#buildTransportModeTransform(InetAddress)
+ * This class is used to build {@link IpSecTransform} objects.
*/
public static class Builder {
private Context mContext;
private IpSecConfig mConfig;
/**
- * Add an encryption algorithm to the transform for the given direction.
+ * Set the encryption algorithm for the given direction.
*
- * <p>If encryption is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If encryption is set for a direction without also providing an SPI for that direction,
+ * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Encryption is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
mConfig.setEncryption(direction, algo);
return this;
}
/**
- * Add an authentication/integrity algorithm to the transform.
+ * Set the authentication (integrity) algorithm for the given direction.
*
- * <p>If authentication is set for a given direction without also providing an SPI for that
- * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ * <p>If authentication is set for a direction without also providing an SPI for that
+ * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
+ * transform.
*
- * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+ * <p>Authentication is mutually exclusive with authenticated encryption.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
+ // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
mConfig.setAuthentication(direction, algo);
return this;
}
/**
- * Add an authenticated encryption algorithm to the transform for the given direction.
+ * Set the authenticated encryption algorithm for the given direction.
*
* <p>If an authenticated encryption algorithm is set for a given direction without also
- * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
- * a build() method.
+ * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
+ * attempting to build the transform.
*
* <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
* Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
- * referred to in RFC 4301)
+ * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
*
* <p>Authenticated encryption is mutually exclusive with encryption and authentication.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
* be applied.
*/
@@ -333,19 +335,16 @@
}
/**
- * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
- * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
- * given destination address.
+ * Set the SPI for the given direction.
*
- * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
- * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
- * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
- * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
+ * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
+ * packets to a given destination address. To prevent SPI collisions, values should be
+ * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}.
*
- * <p>Unless an SPI is set for a given direction, traffic in that direction will be
- * sent/received without any IPsec applied.
+ * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
+ * will not be encrypted or authenticated.
*
- * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
* @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
* traffic
*/
@@ -356,11 +355,10 @@
}
/**
- * Specify the network on which this transform will emit its traffic; (otherwise it will
- * emit on the default network).
+ * Set the {@link Network} which will carry tunneled traffic.
*
- * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
- * tunnel mode.
+ * <p>Restricts the transformed traffic to a particular {@link Network}. This is required
+ * for tunnel mode, otherwise tunneled traffic would be sent on the default network.
*
* @hide
*/
@@ -371,15 +369,18 @@
}
/**
- * Add UDP encapsulation to an IPv4 transform
+ * Add UDP encapsulation to an IPv4 transform.
*
- * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
- * details on how UDP should be applied to IPsec.
+ * <p>This allows IPsec traffic to pass through a NAT.
*
- * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
- * receiving encapsulating traffic.
- * @param remotePort the UDP port number of the remote that will send and receive
- * encapsulated traffic. In the case of IKE, this is likely port 4500.
+ * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+ * ESP Packets</a>
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+ * NAT Traversal of IKEv2</a>
+ *
+ * @param localSocket a socket for sending and receiving encapsulated traffic
+ * @param remotePort the UDP port number of the remote host that will send and receive
+ * encapsulated traffic. In the case of IKEv2, this should be port 4500.
*/
public IpSecTransform.Builder setIpv4Encapsulation(
IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
@@ -393,12 +394,15 @@
// TODO: Probably a better exception to throw for NATTKeepalive failure
// TODO: Specify the needed NATT keepalive permission.
/**
- * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
- * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
- * be activated, then the transform will fail to activate and throw an IOException.
+ * Set NAT-T keepalives to be sent with a given interval.
*
- * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
- * than 20s and no more than 3600s.
+ * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
+ * keepalive is requested but cannot be activated, then creation of an {@link
+ * IpSecTransform} will fail when calling the build method.
+ *
+ * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
+ * between 20s and 3600s.
+ *
* @hide
*/
@SystemApi
@@ -408,36 +412,29 @@
}
/**
- * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
- * Some parameters have interdependencies that are checked at build time. If a well-formed
- * transform cannot be created from the supplied parameters, this method will throw an
- * Exception.
+ * Build a transport mode {@link IpSecTransform}.
*
- * <p>Upon a successful return from this call, the provided IpSecTransform will be active
- * and may be applied to sockets. If too many IpSecTransform objects are active for a given
- * user this operation will fail and throw ResourceUnavailableException. To avoid these
- * exceptions, unused Transform objects must be cleaned up by calling {@link
- * IpSecTransform#close()} when they are no longer needed.
+ * <p>This builds and activates a transport mode transform. Note that an active transform
+ * will not affect any network traffic until it has been applied to one or more sockets.
*
- * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
- * socket will cause the transform to be applied.
- * <p>Note that an active transform will not impact any network traffic until it has
- * been applied to one or more Sockets. Calling this method is a necessary precondition
- * for applying it to a socket, but is not sufficient to actually apply IPsec.
+ * @see IpSecManager#applyTransportModeTransform
+ *
+ * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
+ * this transform
* @throws IllegalArgumentException indicating that a particular combination of transform
- * properties is invalid.
- * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
- * may be allocated
- * @throws SpiUnavailableException if the SPI collides with an existing transform
- * (unlikely).
- * @throws ResourceUnavailableException if the current user currently has exceeded the
- * number of allowed active transforms.
+ * properties is invalid
+ * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
+ * active
+ * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+ * collides with an existing transform
+ * @throws IOException indicating other errors
*/
public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
mConfig.setMode(MODE_TRANSPORT);
mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+ // FIXME: modifying a builder after calling build can change the built transform.
return new IpSecTransform(mContext, mConfig).activate();
}
@@ -465,9 +462,9 @@
}
/**
- * Create a new IpSecTransform.Builder to construct an IpSecTransform
+ * Create a new IpSecTransform.Builder.
*
- * @param context current Context
+ * @param context current context
*/
public Builder(@NonNull Context context) {
Preconditions.checkNotNull(context);
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6e0f70c1..843bdb5 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -18,6 +18,7 @@
import android.annotation.SystemService;
import android.content.Context;
+import android.content.Intent;
import android.hardware.health.V1_0.Constants;
import com.android.internal.app.IBatteryStats;
@@ -56,6 +57,14 @@
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Boolean field indicating whether the battery is currently considered to be
+ * low, that is whether a {@link Intent#ACTION_BATTERY_LOW} broadcast
+ * has been sent.
+ */
+ public static final String EXTRA_BATTERY_LOW = "battery_low";
+
+ /**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the maximum battery level.
*/
public static final String EXTRA_SCALE = "scale";
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a8bd940..af91e81 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -222,8 +222,10 @@
* - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
* New in version 27:
* - Always On Display (screen doze mode) time and power
+ * New in version 28:
+ * - Light/Deep Doze power
*/
- static final int CHECKIN_VERSION = 27;
+ static final int CHECKIN_VERSION = 28;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -2696,6 +2698,18 @@
public abstract long getUahDischarge(int which);
/**
+ * @return the amount of battery discharge while the device is in light idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeLightDoze(int which);
+
+ /**
+ * @return the amount of battery discharge while the device is in deep idle mode, measured in
+ * micro-Ampere-hours.
+ */
+ public abstract long getUahDischargeDeepDoze(int which);
+
+ /**
* Returns the estimated real battery capacity, which may be less than the capacity
* declared by the PowerProfile.
* @return The estimated battery capacity in mAh.
@@ -3327,6 +3341,8 @@
final long dischargeCount = getUahDischarge(which);
final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3497,14 +3513,16 @@
getDischargeStartLevel()-getDischargeCurrentLevel(),
getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
} else {
dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
getDischargeAmountScreenOnSinceCharge(),
getDischargeAmountScreenOffSinceCharge(),
dischargeCount / 1000, dischargeScreenOffCount / 1000,
- getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
+ getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000,
+ dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
}
if (reqUid < 0) {
@@ -4169,6 +4187,26 @@
pw.println(sb.toString());
}
+ final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+ if (dischargeLightDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device light doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
+ final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+ if (dischargeDeepDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Device deep doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
pw.print(" Start clock time: ");
pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
@@ -7131,6 +7169,10 @@
getUahDischargeScreenOff(which) / 1000);
proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
getUahDischargeScreenDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE,
+ getUahDischargeLightDoze(which) / 1000);
+ proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE,
+ getUahDischargeDeepDoze(which) / 1000);
proto.end(bdToken);
// Time remaining
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d092018..36121d4 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1780,7 +1780,7 @@
final int truncatedSize = Math.min(stackTrace.length, 5);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < truncatedSize; i++) {
- sb.append("\tat ").append(stackTrace[i]);
+ sb.append("\tat ").append(stackTrace[i]).append('\n');
}
writeString(sb.toString());
final int payloadPosition = dataPosition();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0fce7a4..5dd8d05 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -387,6 +387,12 @@
public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
/**
+ * Go to sleep reason code: Going to sleep by request of an accessibility service
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+ /**
* Go to sleep flag: Skip dozing state and directly go to full sleep.
* @hide
*/
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index ad9fbfb..6a62424 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -105,6 +105,9 @@
ShellCallback(Parcel in) {
mLocal = false;
mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+ if (mShellCallback != null) {
+ Binder.allowBlocking(mShellCallback.asBinder());
+ }
}
public static final Parcelable.Creator<ShellCallback> CREATOR
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d75219f..fa05a5e 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -91,7 +91,13 @@
mCmd = cmd;
mResultReceiver = resultReceiver;
- if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+ if (DEBUG) {
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+ Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+ }
int res = -1;
try {
res = onCommand(mCmd);
@@ -227,15 +233,19 @@
* @hide
*/
public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+ if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
try {
ParcelFileDescriptor pfd = getShellCallback().openFile(path,
"u:r:system_server:s0", mode);
if (pfd != null) {
+ if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
return pfd;
}
} catch (RuntimeException e) {
+ if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
getErrPrintWriter().println("Failure opening file: " + e.getMessage());
}
+ if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
getErrPrintWriter().println("Error: Unable to open file: " + path);
getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
return null;
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index cd1b190..d07b2ac 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -381,4 +381,9 @@
* Sets callback to DragDropController.
*/
public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+ /**
+ * @see android.view.IWindowManager#lockNow
+ */
+ public abstract void lockNow();
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 7519fce..fbdf17d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@
STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
STATE_CACHED_ACTIVITY_CLIENT, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_RECENT
STATE_CACHED_EMPTY, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f..a1e6fd8 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@
import java.io.Closeable;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.List;
@@ -118,6 +119,17 @@
return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
}
+ public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+ final long[] apkHandles = new long[1];
+ final String path = lite.baseCodePath;
+ apkHandles[0] = nativeOpenApkFd(fd, path);
+ if (apkHandles[0] == 0) {
+ throw new IOException("Unable to open APK " + path + " from fd " + fd);
+ }
+
+ return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+ }
+
Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
boolean debuggable) {
this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@
}
private static native long nativeOpenApk(String path);
+ private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
private static native void nativeClose(long handle);
private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 59a7995..e765ab1 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@
import libcore.io.IoUtils;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
@@ -383,9 +384,15 @@
public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
throws IOException {
+ return calculateInstalledSize(pkg, abiOverride, null);
+ }
+
+ public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+ FileDescriptor fd) throws IOException {
NativeLibraryHelper.Handle handle = null;
try {
- handle = NativeLibraryHelper.Handle.create(pkg);
+ handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+ : NativeLibraryHelper.Handle.create(pkg);
return calculateInstalledSize(pkg, handle, abiOverride);
} finally {
IoUtils.closeQuietly(handle);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f2483c0..6ede72d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -599,6 +599,8 @@
private LongSamplingCounter mDischargeScreenOffCounter;
private LongSamplingCounter mDischargeScreenDozeCounter;
private LongSamplingCounter mDischargeCounter;
+ private LongSamplingCounter mDischargeLightDozeCounter;
+ private LongSamplingCounter mDischargeDeepDozeCounter;
static final int MAX_LEVEL_STEPS = 200;
@@ -697,6 +699,16 @@
}
@Override
+ public long getUahDischargeLightDoze(int which) {
+ return mDischargeLightDozeCounter.getCountLocked(which);
+ }
+
+ @Override
+ public long getUahDischargeDeepDoze(int which) {
+ return mDischargeDeepDozeCounter.getCountLocked(which);
+ }
+
+ @Override
public int getEstimatedBatteryCapacity() {
return mEstimatedBatteryCapacity;
}
@@ -9085,6 +9097,8 @@
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
@@ -9664,6 +9678,8 @@
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
mDischargeScreenDozeCounter.reset(false);
+ mDischargeLightDozeCounter.reset(false);
+ mDischargeDeepDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -11263,6 +11279,11 @@
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11308,6 +11329,11 @@
if (isScreenDoze(mScreenState)) {
mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
}
+ if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+ mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+ } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+ mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
changed = true;
@@ -12069,6 +12095,8 @@
mDischargeCounter.readSummaryFromParcelLocked(in);
mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeLightDozeCounter.readSummaryFromParcelLocked(in);
+ mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in);
int NPKG = in.readInt();
if (NPKG > 0) {
mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -12493,6 +12521,8 @@
mDischargeCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out);
+ mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out);
if (mDailyPackageChanges != null) {
final int NPKG = mDailyPackageChanges.size();
out.writeInt(NPKG);
@@ -13044,6 +13074,8 @@
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTime = in.readLong();
mRpmStats.clear();
@@ -13230,6 +13262,8 @@
mDischargeCounter.writeToParcel(out);
mDischargeScreenOffCounter.writeToParcel(out);
mDischargeScreenDozeCounter.writeToParcel(out);
+ mDischargeLightDozeCounter.writeToParcel(out);
+ mDischargeDeepDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(mRpmStats.size());
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fce5dd5..17b98da 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -557,6 +557,23 @@
return reinterpret_cast<jlong>(zipFile);
}
+static jlong
+com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
+ jobject fileDescriptor, jstring debugPathName)
+{
+ ScopedUtfChars debugFilePath(env, debugPathName);
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ ZipFileRO* zipFile = ZipFileRO::openFd(fd, debugFilePath.c_str());
+
+ return reinterpret_cast<jlong>(zipFile);
+}
+
static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
{
@@ -567,6 +584,9 @@
{"nativeOpenApk",
"(Ljava/lang/String;)J",
(void *)com_android_internal_content_NativeLibraryHelper_openApk},
+ {"nativeOpenApkFd",
+ "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+ (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
{"nativeClose",
"(J)V",
(void *)com_android_internal_content_NativeLibraryHelper_close},
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index c33c0a0..0a3344f 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -120,6 +120,14 @@
// via a coulomb counter. For historical reasons, total_mah_screen_doze is
// a subset of total_mah_screen_off.
optional int64 total_mah_screen_doze = 8;
+ // Total amount of battery discharged in mAh while the device was in light doze mode.
+ // This will only be non-zero for devices that report battery discharge
+ // via a coulomb counter.
+ optional int64 total_mah_light_doze = 9;
+ // Total amount of battery discharged in mAh while the device was in deep doze mode.
+ // This will only be non-zero for devices that report battery discharge
+ // via a coulomb counter.
+ optional int64 total_mah_deep_doze = 10;
};
optional BatteryDischarge battery_discharge = 2;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86103e4..d15c075 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -485,7 +485,6 @@
<protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
<protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
- <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
<protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
<protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
@@ -504,6 +503,8 @@
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
<protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
<protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
<protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -3958,6 +3959,10 @@
<service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- </application>
+
+ <service android:name="com.android.server.display.BrightnessIdleJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+</application>
</manifest>
diff --git a/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
new file mode 100644
index 0000000..89e58aa
--- /dev/null
+++ b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="?attr/dialogPreferredPadding"
+ android:paddingLeft="?attr/dialogPreferredPadding"
+ android:paddingRight="?attr/dialogPreferredPadding">
+
+ <CheckBox
+ android:id="@+id/ask_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:text="@string/unsupported_compile_sdk_show" />
+</FrameLayout>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index f26d6ed..e12f04a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -21,9 +21,10 @@
for watch products. Do not translate. -->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Only show settings item due to smaller real estate. -->
+ <!-- Show smaller list of items due to smaller real estate. -->
<string-array translatable="false" name="config_globalActionsList">
- <item>assist</item>
+ <item>power</item>
+ <item>restart</item>
</string-array>
<!-- Base "touch slop" value used by ViewConfiguration as a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index be0f6d9..fe8ca56 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1340,6 +1340,23 @@
<p>The default value of this attribute is <code>1</code>. -->
<attr name="targetSandboxVersion" format="integer" />
+ <!-- The user-visible SDK version (ex. 26) of the framework against which the application was
+ compiled. This attribute is automatically specified by the Android build tools and should
+ NOT be manually specified.
+ <p>
+ This attribute is the compile-time equivalent of
+ {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. -->
+ <attr name="compileSdkVersion" format="integer" />
+
+ <!-- The development codename (ex. "O") of the framework against which the application was
+ compiled, or "REL" if the application was compiled against a release build. This attribute
+ is automatically specified by the Android build tools and should NOT be manually
+ specified.
+ <p>
+ This attribute is the compile-time equivalent of
+ {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. -->
+ <attr name="compileSdkVersionCodename" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -1369,6 +1386,8 @@
<attr name="isolatedSplits" />
<attr name="isFeatureSplit" />
<attr name="targetSandboxVersion" />
+ <attr name="compileSdkVersion" />
+ <attr name="compileSdkVersionCodename" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edd793d..c104616 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -940,9 +940,16 @@
1 - Global actions menu
2 - Power off (with confirmation)
3 - Power off (without confirmation)
+ 4 - Go to voice assist
-->
<integer name="config_longPressOnPowerBehavior">1</integer>
+ <!-- Control the behavior when the user long presses the power button for a long time.
+ 0 - Nothing
+ 1 - Global actions menu
+ -->
+ <integer name="config_veryLongPressOnPowerBehavior">0</integer>
+
<!-- Control the behavior when the user long presses the back button. Non-zero values are only
valid for watches as part of CDD/CTS.
0 - Nothing
@@ -986,6 +993,9 @@
-->
<integer name="config_shortPressOnSleepBehavior">0</integer>
+ <!-- Time to wait while a button is pressed before triggering a very long press. -->
+ <integer name="config_veryLongPressTimeout">6000</integer>
+
<!-- Package name for default keyguard appwidget [DO NOT TRANSLATE] -->
<string name="widget_default_package_name" translatable="false"></string>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fdd56c4..d3533fe 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2848,6 +2848,10 @@
<public name="ttcIndex" />
<public name="fontVariationSettings" />
<public name="dialogCornerRadius" />
+ <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+ <public name="compileSdkVersion" />
+ <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+ <public name="compileSdkVersionCodename" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7a3fa1a..999ba73 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2852,6 +2852,13 @@
<!-- [CHAR LIMIT=50] Unsupported display size dialog: check box label. -->
<string name="unsupported_display_size_show">Always show</string>
+ <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. -->
+ <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
+ <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. -->
+ <string name="unsupported_compile_sdk_show">Always show</string>
+ <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. -->
+ <string name="unsupported_compile_sdk_check_update">Check for update</string>
+
<!-- Text of the alert that is displayed when an application has violated StrictMode. -->
<string name="smv_application">The app <xliff:g id="application">%1$s</xliff:g>
(process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5497085..a115816 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -419,6 +419,8 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_veryLongPressTimeout" />
<java-symbol type="integer" name="config_longPressOnBackBehavior" />
<java-symbol type="integer" name="config_backPanicBehavior" />
<java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
@@ -3151,4 +3153,9 @@
<!-- From media projection -->
<java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
<java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" />
+
+ <!-- Compile SDK check -->
+ <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" />
+ <java-symbol type="string" name="unsupported_compile_sdk_message" />
+ <java-symbol type="string" name="unsupported_compile_sdk_check_update" />
</resources>
diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS
new file mode 100644
index 0000000..031a6c1
--- /dev/null
+++ b/data/keyboards/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+michaelwr@google.com
+svv@google.com
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 3c8736e..0485625 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -148,11 +148,15 @@
int count = android_atomic_dec(&gCount);
if (kIsDebug) {
ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+ } else {
+ ALOGV("Destroying AssetManager in %p #%d\n", this, count);
}
// Manually close any fd paths for which we have not yet opened their zip (which
// will take ownership of the fd and close it when done).
for (size_t i=0; i<mAssetPaths.size(); i++) {
+ ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd,
+ mAssetPaths[i].zip.get());
if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
close(mAssetPaths[i].rawFd);
}
@@ -202,7 +206,7 @@
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
ap.isSystemAsset = isSystemAsset;
- mAssetPaths.add(ap);
+ ssize_t apPos = mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
@@ -219,7 +223,7 @@
#endif
if (mResources != NULL) {
- appendPathToResTable(ap, appAsLib);
+ appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
}
return true;
@@ -304,7 +308,7 @@
ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
- mAssetPaths.add(ap);
+ ssize_t apPos = mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
@@ -312,7 +316,7 @@
}
if (mResources != NULL) {
- appendPathToResTable(ap, appAsLib);
+ appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
}
return true;
@@ -442,7 +446,8 @@
i--;
ALOGV("Looking for asset '%s' in '%s'\n",
assetName.string(), mAssetPaths.itemAt(i).path.string());
- Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+ Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode,
+ mAssetPaths.editItemAt(i));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -471,7 +476,7 @@
i--;
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
Asset* pAsset = openNonAssetInPathLocked(
- fileName, mode, mAssetPaths.itemAt(i));
+ fileName, mode, mAssetPaths.editItemAt(i));
if (pAsset != NULL) {
if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1);
return pAsset != kExcludedAsset ? pAsset : NULL;
@@ -493,7 +498,7 @@
ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
mAssetPaths.itemAt(which).path.string());
Asset* pAsset = openNonAssetInPathLocked(
- fileName, mode, mAssetPaths.itemAt(which));
+ fileName, mode, mAssetPaths.editItemAt(which));
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -527,7 +532,7 @@
}
}
-bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
+bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
@@ -645,7 +650,8 @@
bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
- bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
+ bool empty = appendPathToResTable(
+ const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i));
onlyEmptyResources = onlyEmptyResources && empty;
}
@@ -770,7 +776,7 @@
* be used.
*/
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& ap)
+ asset_path& ap)
{
Asset* pAsset = NULL;
@@ -851,17 +857,19 @@
* Return a pointer to one of our open Zip archives. Returns NULL if no
* matching Zip file exists.
*/
-ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap)
{
- ALOGV("getZipFileLocked() in %p\n", this);
+ ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get());
if (ap.zip != NULL) {
return ap.zip->getZip();
}
if (ap.rawFd < 0) {
+ ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string());
ap.zip = mZipSet.getSharedZip(ap.path);
} else {
+ ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd);
ap.zip = SharedZip::create(ap.rawFd, ap.path);
}
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 4254614..ecc5dc1 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -222,16 +222,16 @@
bool isSystemOverlay;
bool isSystemAsset;
bool assumeOwnership;
- mutable sp<SharedZip> zip;
+ sp<SharedZip> zip;
};
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
- const asset_path& path);
+ asset_path& path);
String8 createPathNameLocked(const asset_path& path, const char* rootDir);
String8 createZipSourceNameLocked(const String8& zipFileName,
const String8& dirName, const String8& fileName);
- ZipFileRO* getZipFileLocked(const asset_path& path);
+ ZipFileRO* getZipFileLocked(asset_path& path);
Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
const ZipEntryRO entry, AccessMode mode, const String8& entryName);
@@ -247,7 +247,7 @@
const ResTable* getResTable(bool required = true) const;
void setLocaleLocked(const char* locale);
void updateResourceParamsLocked() const;
- bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const;
+ bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const;
Asset* openIdmapLocked(const struct asset_path& ap) const;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c475e12..306ed83 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -721,14 +721,16 @@
/**
* A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is
* selected in the absence of a specific user choice.
- * This is currently only used for subtitle tracks, when the user selected
- * 'Default' for the captioning locale.
+ * This is currently used in two scenarios:
+ * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
+ * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
+ * primary item in the file.
+
* The associated value is an integer, where non-0 means TRUE. This is an optional
* field; if not specified, DEFAULT is considered to be FALSE.
*/
public static final String KEY_IS_DEFAULT = "is-default";
-
/**
* A key for the FORCED field for subtitle tracks. True if it is a
* forced subtitle track. Forced subtitle tracks are essential for the
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 91e57ee..02c71b2 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -258,12 +258,18 @@
* in include/media/stagefright/MediaMuxer.h!
*/
private OutputFormat() {}
+ /** @hide */
+ public static final int MUXER_OUTPUT_FIRST = 0;
/** MPEG4 media file format*/
- public static final int MUXER_OUTPUT_MPEG_4 = 0;
+ public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST;
/** WEBM media file format*/
- public static final int MUXER_OUTPUT_WEBM = 1;
+ public static final int MUXER_OUTPUT_WEBM = MUXER_OUTPUT_FIRST + 1;
/** 3GPP media file format*/
- public static final int MUXER_OUTPUT_3GPP = 2;
+ public static final int MUXER_OUTPUT_3GPP = MUXER_OUTPUT_FIRST + 2;
+ /** HEIF media file format*/
+ public static final int MUXER_OUTPUT_HEIF = MUXER_OUTPUT_FIRST + 3;
+ /** @hide */
+ public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_HEIF;
};
/** @hide */
@@ -271,6 +277,7 @@
OutputFormat.MUXER_OUTPUT_MPEG_4,
OutputFormat.MUXER_OUTPUT_WEBM,
OutputFormat.MUXER_OUTPUT_3GPP,
+ OutputFormat.MUXER_OUTPUT_HEIF,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Format {}
@@ -347,8 +354,7 @@
}
private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
- if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM
- && format != OutputFormat.MUXER_OUTPUT_3GPP) {
+ if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) {
throw new IllegalArgumentException("format: " + format + " is invalid");
}
mNativeObject = nativeSetup(fd, format);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..0f498bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -910,6 +910,7 @@
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), this);
+ SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
@@ -937,6 +938,7 @@
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
+ SystemUIDialog.setWindowOnTop(this);
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7d..5867006d 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,7 +21,11 @@
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -72,6 +76,9 @@
case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
return toggleSplitScreen();
}
+ case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+ return lockScreen();
+ }
}
return false;
} finally {
@@ -153,4 +160,11 @@
}
return true;
}
+
+ private boolean lockScreen() {
+ mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+ mWindowManagerService.lockNow();
+ return true;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0..dd29a04 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -48,7 +48,6 @@
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
-import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -103,6 +102,7 @@
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.internal.PerformInitializeTask;
import com.android.server.backup.internal.ProvisionedObserver;
@@ -117,6 +117,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -1585,8 +1586,27 @@
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
}
+ // We're using pieces of the new binding on-demand infra-structure and the old always-bound
+ // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
+ // is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
+ // This is weird but there is a reason.
+ // This is the natural place to put TransportManager.getCurrentTransportClient() because of
+ // the null handling below that should be the same for TransportClient.
+ // TransportClient.connect() would return a IBackupTransport for us (instead of using the
+ // old infra), but it may block and we don't want this in this thread.
+ // The only usage of transport in this method is for transport.transportDirName(). When the
+ // push-from-transport part of binding on-demand is in place we will replace the calls for
+ // IBackupTransport.transportDirName() with calls for
+ // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
+ // here until we implement that.
+ // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport == null) {
+ if (transportClient == null || transport == null) {
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
+ }
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1594,6 +1614,9 @@
return BackupManager.ERROR_TRANSPORT_ABORTED;
}
+ OnTaskFinishedListener listener =
+ caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+
ArrayList<String> fullBackupList = new ArrayList<>();
ArrayList<String> kvBackupList = new ArrayList<>();
for (String packageName : packages) {
@@ -1640,8 +1663,8 @@
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
- msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
- monitor, true, nonIncrementalBackup);
+ msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
+ monitor, listener, true, nonIncrementalBackup);
mBackupHandler.sendMessage(msg);
return BackupManager.SUCCESS;
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 7a0173f..a2b5cb8 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,8 +16,9 @@
package com.android.server.backup;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
-import android.app.backup.SelectBackupTransportCallback;
+import android.app.backup.BackupTransport;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,9 +45,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -60,8 +63,7 @@
private static final String TAG = "BackupTransportManager";
@VisibleForTesting
- /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
- "android.backup.TRANSPORT_HOST";
+ public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -72,6 +74,7 @@
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final Handler mHandler;
+ private final TransportClientManager mTransportClientManager;
/**
* This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -95,6 +98,10 @@
@GuardedBy("mTransportLock")
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+ /** Names of transports we've bound to at least once */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+
/**
* Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
*/
@@ -123,6 +130,7 @@
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
+ mTransportClientManager = new TransportClientManager(context);
}
void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@
return null;
}
+ /**
+ * Returns the transport name associated with {@param transportClient} or {@code null} if not
+ * found.
+ */
+ @Nullable
+ public String getTransportName(TransportClient transportClient) {
+ ComponentName transportComponent = transportClient.getTransportComponent();
+ synchronized (mTransportLock) {
+ for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
+ if (transportEntry.getValue().equals(transportComponent)) {
+ return transportEntry.getKey();
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
+ *
+ * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getTransportClient(String transportName, String caller) {
+ ComponentName transportComponent = mTransportsByName.get(transportName);
+ if (transportComponent == null) {
+ Slog.w(TAG, "Transport " + transportName + " not registered");
+ return null;
+ }
+ return mTransportClientManager.getTransportClient(transportComponent, caller);
+ }
+
+ /**
+ * Returns a {@link TransportClient} for the current transport or null if not found.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not found.
+ */
+ @Nullable
+ public TransportClient getCurrentTransportClient(String caller) {
+ return getTransportClient(mCurrentTransportName, caller);
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+ }
+
String[] getBoundTransportNames() {
synchronized (mTransportLock) {
return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
@@ -374,6 +443,7 @@
String componentShortString = component.flattenToShortString().intern();
if (success) {
Slog.d(TAG, "Bound to transport: " + componentShortString);
+ mTransportsByName.put(mTransportName, component);
mBoundTransports.put(mTransportName, component);
for (TransportReadyCallback listener : mListeners) {
listener.onSuccess(mTransportName);
@@ -528,7 +598,7 @@
// These only exists to make it testable with Robolectric, which is not updated to API level 24
// yet.
// TODO: Get rid of this once Robolectric is updated.
- private static UserHandle createSystemUserHandle() {
+ public static UserHandle createSystemUserHandle() {
return new UserHandle(UserHandle.USER_SYSTEM);
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 8f82300..9011b95 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -38,6 +38,8 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.TransportManager;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -51,10 +53,8 @@
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
/**
* Asynchronous backup/restore handler thread.
@@ -81,7 +81,7 @@
public static final int MSG_BACKUP_RESTORE_STEP = 20;
public static final int MSG_OP_COMPLETE = 21;
- private RefactoredBackupManagerService backupManagerService;
+ private final RefactoredBackupManagerService backupManagerService;
public BackupHandler(
RefactoredBackupManagerService backupManagerService, Looper looper) {
@@ -91,13 +91,23 @@
public void handleMessage(Message msg) {
+ TransportManager transportManager = backupManagerService.getTransportManager();
switch (msg.what) {
case MSG_RUN_BACKUP: {
backupManagerService.setLastBackupPass(System.currentTimeMillis());
+ String callerLogString = "BH/MSG_RUN_BACKUP";
+ TransportClient transportClient =
+ transportManager.getCurrentTransportClient(callerLogString);
IBackupTransport transport =
- backupManagerService.getTransportManager().getCurrentTransportBinder();
+ transportClient != null
+ ? transportClient.connect(callerLogString)
+ : null;
if (transport == null) {
+ if (transportClient != null) {
+ transportManager
+ .disposeOfTransportClient(transportClient, callerLogString);
+ }
Slog.v(TAG, "Backup requested but no transport available");
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -138,9 +148,13 @@
// Spin up a backup state sequence and set it running
try {
String dirName = transport.transportDirName();
+ OnTaskFinishedListener listener =
+ caller ->
+ transportManager
+ .disposeOfTransportClient(transportClient, caller);
PerformBackupTask pbt = new PerformBackupTask(
- backupManagerService, transport, dirName, queue,
- oldJournal, null, null, Collections.<String>emptyList(), false,
+ backupManagerService, transportClient, dirName, queue,
+ oldJournal, null, null, listener, Collections.emptyList(), false,
false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
@@ -157,6 +171,7 @@
}
if (!staged) {
+ transportManager.disposeOfTransportClient(transportClient, callerLogString);
// if we didn't actually hand off the wakelock, rewind until next time
synchronized (backupManagerService.getQueueLock()) {
backupManagerService.setBackupRunning(false);
@@ -382,9 +397,9 @@
PerformBackupTask pbt = new PerformBackupTask(
backupManagerService,
- params.transport, params.dirName,
- kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
- params.nonIncrementalBackup);
+ params.transportClient, params.dirName,
+ kvQueue, null, params.observer, params.monitor, params.listener,
+ params.fullPackages, true, params.nonIncrementalBackup);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
break;
diff --git a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 0000000..e417f06
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.backup.internal;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportConnectionListener;
+
+/** Listener to be called when a task finishes, successfully or not. */
+public interface OnTaskFinishedListener {
+ OnTaskFinishedListener NOP = caller -> {};
+
+ /**
+ * Called when a task finishes, successfully or not.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ void onFinished(String caller);
+}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c0caa557..1fa215a 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -63,6 +63,8 @@
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportUtils;
import com.android.server.backup.utils.AppBackupUtils;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
import com.android.server.backup.utils.BackupObserverUtils;
@@ -112,7 +114,6 @@
private RefactoredBackupManagerService backupManagerService;
private final Object mCancelLock = new Object();
- IBackupTransport mTransport;
ArrayList<BackupRequest> mQueue;
ArrayList<BackupRequest> mOriginalQueue;
File mStateDir;
@@ -122,6 +123,8 @@
IBackupObserver mObserver;
IBackupManagerMonitor mMonitor;
+ private final TransportClient mTransportClient;
+ private final OnTaskFinishedListener mListener;
private final PerformFullTransportBackupTask mFullBackupTask;
private final int mCurrentOpToken;
private volatile int mEphemeralOpToken;
@@ -143,17 +146,19 @@
private volatile boolean mCancelAll;
public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
- IBackupTransport transport, String dirName,
+ TransportClient transportClient, String dirName,
ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
IBackupObserver observer, IBackupManagerMonitor monitor,
- List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
+ @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
+ boolean userInitiated, boolean nonIncremental) {
this.backupManagerService = backupManagerService;
- mTransport = transport;
+ mTransportClient = transportClient;
mOriginalQueue = queue;
mQueue = new ArrayList<>();
mJournal = journal;
mObserver = observer;
mMonitor = monitor;
+ mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
mPendingFullBackups = pendingFullBackups;
mUserInitiated = userInitiated;
mNonIncremental = nonIncremental;
@@ -289,10 +294,10 @@
if (DEBUG) {
Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
}
-
File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
try {
- final String transportName = mTransport.transportDirName();
+ IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
+ final String transportName = transport.transportDirName();
EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
// If we haven't stored package manager metadata yet, we must init the transport.
@@ -300,7 +305,7 @@
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
backupManagerService.addBackupTrace("initializing transport " + transportName);
backupManagerService.resetBackupState(mStateDir); // Just to make sure.
- mStatus = mTransport.initializeDevice();
+ mStatus = transport.initializeDevice();
backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -324,7 +329,7 @@
PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
mStatus = invokeAgentForBackup(
PACKAGE_MANAGER_SENTINEL,
- IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+ IBackupAgent.Stub.asInterface(pmAgent.onBind()));
backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
// Because the PMBA is a local instance, it has already executed its
@@ -445,7 +450,7 @@
backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
if (agent != null) {
mAgentBinder = agent;
- mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+ mStatus = invokeAgentForBackup(request.packageName, agent);
// at this point we'll either get a completion callback from the
// agent, or a timeout message on the main handler. either way, we're
// done here as long as we're successful so far.
@@ -526,11 +531,14 @@
// If everything actually went through and this is the first time we've
// done a backup, we can now record what the current backup dataset token
// is.
+ String callerLogString = "PBT.finalizeBackup()";
if ((backupManagerService.getCurrentToken() == 0) && (mStatus
== BackupTransport.TRANSPORT_OK)) {
backupManagerService.addBackupTrace("success; recording token");
try {
- backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow(callerLogString);
+ backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
backupManagerService.writeRestoreTokens();
} catch (Exception e) {
// nothing for it at this point, unfortunately, but this will be
@@ -553,13 +561,13 @@
backupManagerService.addBackupTrace("init required; rerunning");
try {
final String name = backupManagerService.getTransportManager().getTransportName(
- mTransport);
+ mTransportClient);
if (name != null) {
backupManagerService.getPendingInits().add(name);
} else {
if (DEBUG) {
- Slog.w(TAG, "Couldn't find name of transport " + mTransport
- + " for init");
+ Slog.w(TAG, "Couldn't find name of transport "
+ + mTransportClient.getTransportComponent() + " for init");
}
}
} catch (Exception e) {
@@ -577,17 +585,21 @@
if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+ // TODO(brufino): Move the onFinish() call to the full-backup task
+ mListener.onFinished(callerLogString);
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
backupManagerService.getWakelock().acquire();
(new Thread(mFullBackupTask, "full-transport-requested")).start();
} else if (mCancelAll) {
+ mListener.onFinished(callerLogString);
if (mFullBackupTask != null) {
mFullBackupTask.unregisterTask();
}
BackupObserverUtils.sendBackupFinished(mObserver,
BackupManager.ERROR_BACKUP_CANCELLED);
} else {
+ mListener.onFinished(callerLogString);
mFullBackupTask.unregisterTask();
switch (mStatus) {
case BackupTransport.TRANSPORT_OK:
@@ -619,8 +631,7 @@
// Invoke an agent's doBackup() and start a timeout message spinning on the main
// handler in case it doesn't get back to us.
- int invokeAgentForBackup(String packageName, IBackupAgent agent,
- IBackupTransport transport) {
+ int invokeAgentForBackup(String packageName, IBackupAgent agent) {
if (DEBUG) {
Slog.d(TAG, "invokeAgentForBackup on " + packageName);
}
@@ -671,7 +682,10 @@
ParcelFileDescriptor.MODE_CREATE |
ParcelFileDescriptor.MODE_TRUNCATE);
- final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
+
+ final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
callingAgent = true;
// Initiate the target's backup pass
@@ -888,10 +902,12 @@
clearAgentState();
backupManagerService.addBackupTrace("operation complete");
+ IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
ParcelFileDescriptor backupData = null;
mStatus = BackupTransport.TRANSPORT_OK;
long size = 0;
try {
+ TransportUtils.checkTransport(transport);
size = mBackupDataName.length();
if (size > 0) {
if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -899,7 +915,7 @@
ParcelFileDescriptor.MODE_READ_ONLY);
backupManagerService.addBackupTrace("sending data to transport");
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
- mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+ mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -910,7 +926,7 @@
backupManagerService.addBackupTrace("data delivered: " + mStatus);
if (mStatus == BackupTransport.TRANSPORT_OK) {
backupManagerService.addBackupTrace("finishing op on transport");
- mStatus = mTransport.finishBackup();
+ mStatus = transport.finishBackup();
backupManagerService.addBackupTrace("finished: " + mStatus);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
backupManagerService.addBackupTrace("transport rejected package");
@@ -981,8 +997,8 @@
}
if (mAgentBinder != null) {
try {
- long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
- false);
+ TransportUtils.checkTransport(transport);
+ long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
mAgentBinder.doQuotaExceeded(size, quota);
} catch (Exception e) {
Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
@@ -1052,7 +1068,9 @@
// by way of retry/backoff time.
long delay;
try {
- delay = mTransport.requestBackupTime();
+ IBackupTransport transport =
+ mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
+ delay = transport.requestBackupTime();
} catch (Exception e) {
Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
delay = 0; // use the scheduler's default
diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java
index 4fd7ddb..2ba8ec1 100644
--- a/services/backup/java/com/android/server/backup/params/BackupParams.java
+++ b/services/backup/java/com/android/server/backup/params/BackupParams.java
@@ -19,30 +19,34 @@
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.TransportClient;
import java.util.ArrayList;
public class BackupParams {
- public IBackupTransport transport;
+ public TransportClient transportClient;
public String dirName;
public ArrayList<String> kvPackages;
public ArrayList<String> fullPackages;
public IBackupObserver observer;
public IBackupManagerMonitor monitor;
+ public OnTaskFinishedListener listener;
public boolean userInitiated;
public boolean nonIncrementalBackup;
- public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
- ArrayList<String> fullPackages, IBackupObserver observer,
- IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
- this.transport = transport;
+ public BackupParams(TransportClient transportClient, String dirName,
+ ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
+ IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
+ boolean nonIncrementalBackup) {
+ this.transportClient = transportClient;
this.dirName = dirName;
this.kvPackages = kvPackages;
this.fullPackages = fullPackages;
this.observer = observer;
this.monitor = monitor;
+ this.listener = listener;
this.userInitiated = userInitiated;
this.nonIncrementalBackup = nonIncrementalBackup;
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 0000000..9c39729
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,468 @@
+/*
+ * 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.server.backup.transport;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.TransportManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
+ * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
+ * responsible for only one connection to the transport service, not more.
+ *
+ * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
+ * call either {@link #connect(String)}, if you can block your thread, or {@link
+ * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
+ * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
+ * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
+ * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
+ *
+ * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @see TransportManager
+ */
+public class TransportClient {
+ private static final String TAG = "TransportClient";
+
+ private final Context mContext;
+ private final Intent mBindIntent;
+ private final String mIdentifier;
+ private final ComponentName mTransportComponent;
+ private final Handler mListenerHandler;
+ private final String mPrefixForLog;
+ private final Object mStateLock = new Object();
+
+ @GuardedBy("mStateLock")
+ private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
+
+ @GuardedBy("mStateLock")
+ @State
+ private int mState = State.IDLE;
+
+ @GuardedBy("mStateLock")
+ private volatile IBackupTransport mTransport;
+
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier) {
+ this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+ }
+
+ @VisibleForTesting
+ TransportClient(
+ Context context,
+ Intent bindIntent,
+ ComponentName transportComponent,
+ String identifier,
+ Handler listenerHandler) {
+ mContext = context;
+ mTransportComponent = transportComponent;
+ mBindIntent = bindIntent;
+ mIdentifier = identifier;
+ mListenerHandler = listenerHandler;
+
+ // For logging
+ String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
+ mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
+ }
+
+ public ComponentName getTransportComponent() {
+ return mTransportComponent;
+ }
+
+ // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
+ // of these calls, if a binding happen again the new service can be a different instance. Since
+ // transports are stateful, we don't want a new instance responding for an old instance's state.
+ private ServiceConnection mConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder binder) {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ if (mState != State.UNUSABLE) {
+ log(Log.DEBUG, "Transport connected");
+ setStateLocked(State.CONNECTED, transport);
+ notifyListenersAndClearLocked(transport);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ synchronized (mStateLock) {
+ log(Log.ERROR, "Service disconnected: client UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.ERROR, "Binding died: client UNUSABLE");
+ // After unbindService() no calls back to mConnection
+ switch (mState) {
+ case State.UNUSABLE:
+ break;
+ case State.IDLE:
+ log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(this);
+ break;
+ }
+ }
+ }
+ };
+
+ /**
+ * Attempts to connect to the transport (if needed).
+ *
+ * <p>Note that being bound is not the same as connected. To be connected you also need to be
+ * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
+ * binder instance you need to be connected. This method will attempt to connect and return an
+ * usable transport binder regardless of the state of the object, it may already be connected,
+ * or bound but not connected, not bound at all or even unusable.
+ *
+ * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
+ * one of its variants) can be called or not depending on the inner state. However, it won't be
+ * called again if we're already bound. For example, if one was already requested but the
+ * framework has not yet returned (meaning we're bound but still trying to connect) it won't
+ * trigger another one, just piggyback on the original request.
+ *
+ * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
+ * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
+ * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
+ * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
+ * The reasons for a null transport binder are:
+ *
+ * <ul>
+ * <li>Some code called {@link #unbind(String)} before you got a callback.
+ * <li>The framework had already called {@link
+ * ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
+ * ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
+ * Check the documentation of those methods for when that happens.
+ * <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
+ * ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
+ * when this happens.
+ * </ul>
+ *
+ * For unusable transport binders check {@link DeadObjectException}.
+ *
+ * @param listener The listener that will be called with the (possibly null or unusable) {@link
+ * IBackupTransport} instance and this {@link TransportClient} object.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
+ * should be a human-readable short string that is easily identifiable in the logs. Ideally
+ * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
+ * descriptive like MyHandler.handleMessage() you should put something that someone reading
+ * the code would understand, like MyHandler/MSG_FOO.
+ * @see #connect(String)
+ * @see DeadObjectException
+ * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
+ * @see ServiceConnection#onServiceDisconnected(ComponentName)
+ * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+ */
+ public void connectAsync(TransportConnectionListener listener, String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ switch (mState) {
+ case State.UNUSABLE:
+ log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
+ notifyListener(listener, null, caller);
+ break;
+ case State.IDLE:
+ boolean hasBound =
+ mContext.bindServiceAsUser(
+ mBindIntent,
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ TransportManager.createSystemUserHandle());
+ if (hasBound) {
+ // We don't need to set a time-out because we are guaranteed to get a call
+ // back in ServiceConnection, either an onServiceConnected() or
+ // onBindingDied().
+ log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+ setStateLocked(State.BOUND_AND_CONNECTING, null);
+ mListeners.put(listener, caller);
+ } else {
+ log(Log.ERROR, "Async connect: bindService returned false");
+ // mState remains State.IDLE
+ mContext.unbindService(mConnection);
+ notifyListener(listener, null, caller);
+ }
+ break;
+ case State.BOUND_AND_CONNECTING:
+ log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+ mListeners.put(listener, caller);
+ break;
+ case State.CONNECTED:
+ log(Log.DEBUG, caller, "Async connect: reusing transport");
+ notifyListener(listener, mTransport, caller);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Removes the transport binding.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ */
+ public void unbind(String caller) {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+ switch (mState) {
+ case State.UNUSABLE:
+ case State.IDLE:
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.IDLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(mConnection);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.IDLE, null);
+ mContext.unbindService(mConnection);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Attempts to connect to the transport (if needed) and returns it.
+ *
+ * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
+ * same observations about state are valid here. Also, what was said about the {@link
+ * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
+ * value of this method.
+ *
+ * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
+ * threads. You can't call this from the process main-thread (it throws an exception if you do
+ * so).
+ *
+ * <p>In most cases only the first call to this method will block, the following calls should
+ * return instantly. However, this is not guaranteed.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
+ * still be unusable - throws {@link DeadObjectException} on method calls
+ */
+ @WorkerThread
+ @Nullable
+ public IBackupTransport connect(String caller) {
+ // If called on the main-thread this could deadlock waiting because calls to
+ // ServiceConnection are on the main-thread as well
+ Preconditions.checkState(
+ !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
+
+ IBackupTransport transport = mTransport;
+ if (transport != null) {
+ log(Log.DEBUG, caller, "Sync connect: reusing transport");
+ return transport;
+ }
+
+ // If it's already UNUSABLE we return straight away, no need to go to main-thread
+ synchronized (mStateLock) {
+ if (mState == State.UNUSABLE) {
+ log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
+ return null;
+ }
+ }
+
+ CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ TransportConnectionListener requestListener =
+ (requestedTransport, transportClient) ->
+ transportFuture.complete(requestedTransport);
+
+ log(Log.DEBUG, caller, "Sync connect: calling async");
+ connectAsync(requestListener, caller);
+
+ try {
+ return transportFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ String error = e.getClass().getSimpleName();
+ log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
+ *
+ * <p>Same as {@link #connect(String)} except it throws instead of returning null.
+ *
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link #connectAsync(TransportConnectionListener, String)} for more details.
+ * @return A {@link IBackupTransport} transport binder instance.
+ * @see #connect(String)
+ * @throws TransportNotAvailableException if connection attempt fails.
+ */
+ @WorkerThread
+ public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
+ IBackupTransport transport = connect(caller);
+ if (transport == null) {
+ log(Log.ERROR, caller, "Transport connection failed");
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ @Override
+ public String toString() {
+ return "TransportClient{"
+ + mTransportComponent.flattenToShortString()
+ + "#"
+ + mIdentifier
+ + "}";
+ }
+
+ private void notifyListener(
+ TransportConnectionListener listener, IBackupTransport transport, String caller) {
+ log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
+ mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
+ }
+
+ @GuardedBy("mStateLock")
+ private void notifyListenersAndClearLocked(IBackupTransport transport) {
+ for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
+ TransportConnectionListener listener = entry.getKey();
+ String caller = entry.getValue();
+ notifyListener(listener, transport, caller);
+ }
+ mListeners.clear();
+ }
+
+ @GuardedBy("mStateLock")
+ private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+ mState = state;
+ mTransport = transport;
+ }
+
+ @GuardedBy("mStateLock")
+ private void checkStateIntegrityLocked() {
+ switch (mState) {
+ case State.UNUSABLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
+ checkState(
+ mTransport == null, "Transport expected to be null when state = UNUSABLE");
+ case State.IDLE:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
+ checkState(mTransport == null, "Transport expected to be null when state = IDLE");
+ break;
+ case State.BOUND_AND_CONNECTING:
+ checkState(
+ mTransport == null,
+ "Transport expected to be null when state = BOUND_AND_CONNECTING");
+ break;
+ case State.CONNECTED:
+ checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
+ checkState(
+ mTransport != null,
+ "Transport expected to be non-null when state = CONNECTED");
+ break;
+ default:
+ checkState(false, "Unexpected state = " + stateToString(mState));
+ }
+ }
+
+ private void checkState(boolean assertion, String message) {
+ if (!assertion) {
+ log(Log.ERROR, message);
+ }
+ }
+
+ private String stateToString(@State int state) {
+ switch (state) {
+ case State.UNUSABLE:
+ return "UNUSABLE";
+ case State.IDLE:
+ return "IDLE";
+ case State.BOUND_AND_CONNECTING:
+ return "BOUND_AND_CONNECTING";
+ case State.CONNECTED:
+ return "CONNECTED";
+ default:
+ return "<UNKNOWN = " + state + ">";
+ }
+ }
+
+ private void log(int priority, String message) {
+ TransportUtils.log(priority, TAG, message);
+ }
+
+ private void log(int priority, String caller, String msg) {
+ TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
+ // TODO(brufino): Log in internal list for dump
+ // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
+ }
+
+ @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface State {
+ int UNUSABLE = 0;
+ int IDLE = 1;
+ int BOUND_AND_CONNECTING = 2;
+ int CONNECTED = 3;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 0000000..1cbe7471
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ *
+ * <p>TODO(brufino): Implement pool of TransportClients
+ */
+public class TransportClientManager {
+ private static final String TAG = "TransportClientManager";
+
+ private final Context mContext;
+ private final Object mTransportClientsLock = new Object();
+ private int mTransportClientsCreated = 0;
+
+ public TransportClientManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Retrieves a {@link TransportClient} for the transport identified by {@param
+ * transportComponent}.
+ *
+ * @param transportComponent The {@link ComponentName} of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient}.
+ */
+ public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+ Intent bindIntent =
+ new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ synchronized (mTransportClientsLock) {
+ TransportClient transportClient =
+ new TransportClient(
+ mContext,
+ bindIntent,
+ transportComponent,
+ Integer.toString(mTransportClientsCreated));
+ mTransportClientsCreated++;
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
+ return transportClient;
+ }
+ }
+
+ /**
+ * Disposes of the {@link TransportClient}.
+ *
+ * @param transportClient The {@link TransportClient} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+ TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
+ transportClient.unbind(caller);
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 0000000..1ccffd0
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
@@ -0,0 +1,37 @@
+/*
+ * 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.server.backup.transport;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
+ * String)}.
+ */
+public interface TransportConnectionListener {
+ /**
+ * Called when {@link TransportClient} has a transport binder available or that it decided it
+ * couldn't obtain one, in which case {@param transport} is null.
+ *
+ * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient The {@link TransportClient} used to retrieve this transport binder.
+ */
+ void onTransportConnectionResult(
+ @Nullable IBackupTransport transport, TransportClient transportClient);
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 0000000..a02f03c
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.backup.transport;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
+ * TransportClient} connection attempt fails. Check {@link
+ * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
+ *
+ * @see TransportClient#connectAsync(TransportConnectionListener, String)
+ */
+class TransportNotAvailableException extends Exception {
+ TransportNotAvailableException() {
+ super("Transport not available");
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 0000000..514717f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.backup.transport;
+
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+
+/** Utility methods for transport-related operations. */
+public class TransportUtils {
+
+ /**
+ * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
+ * similar to a {@link DeadObjectException} coming from a dead transport binder.
+ */
+ public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
+ throws TransportNotAvailableException {
+ if (transport == null) {
+ throw new TransportNotAvailableException();
+ }
+ return transport;
+ }
+
+ static void log(int priority, String tag, String message) {
+ log(priority, tag, null, message);
+ }
+
+ static void log(int priority, String tag, @Nullable String caller, String message) {
+ log(priority, tag, "", caller, message);
+ }
+
+ static void log(
+ int priority, String tag, String prefix, @Nullable String caller, String message) {
+ if (Log.isLoggable(tag, priority)) {
+ if (caller != null) {
+ prefix += "[" + caller + "] ";
+ }
+ Slog.println(priority, tag, prefix + message);
+ }
+ }
+
+ private TransportUtils() {}
+}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index ea0ed27..924e736 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -619,6 +619,7 @@
intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0fd59ea..bdfccd6e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@
private static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
- private static final String WAKELOCK_KEY = TAG;
+ private static final String WAKELOCK_KEY = "*location*";
// Location resolution level: no location data whatsoever
private static final int RESOLUTION_LEVEL_NONE = 0;
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e5fbdd21..296fb32 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,3 +1,4 @@
+# Connectivity / Networking
per-file ConnectivityService.java=ek@google.com
per-file ConnectivityService.java=hugobenichi@google.com
per-file ConnectivityService.java=lorenzo@google.com
@@ -7,3 +8,9 @@
per-file NsdService.java=ek@google.com
per-file NsdService.java=hugobenichi@google.com
per-file NsdService.java=lorenzo@google.com
+
+# Vibrator
+per-file VibratorService.java=michaelwr@google.com
+
+# Threads
+per-file DisplayThread.java=michaelwr@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b3b831e..ae91b82 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -351,7 +351,6 @@
import android.util.StatsLog;
import android.util.TimingsTraceLog;
import android.util.DebugUtils;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -716,6 +715,8 @@
final AppErrors mAppErrors;
+ final AppWarnings mAppWarnings;
+
/**
* Dump of the activity state at the time of the last ANR. Cleared after
* {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS}
@@ -1726,7 +1727,6 @@
static final int IDLE_UIDS_MSG = 58;
static final int LOG_STACK_STATE = 60;
static final int VR_MODE_CHANGE_MSG = 61;
- static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
static final int NOTIFY_VR_SLEEPING_MSG = 65;
static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
@@ -1745,7 +1745,6 @@
static KillHandler sKillHandler = null;
CompatModeDialog mCompatModeDialog;
- UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
long mLastMemUsageReportTime = 0;
/**
@@ -1927,23 +1926,6 @@
}
break;
}
- case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: {
- synchronized (ActivityManagerService.this) {
- final ActivityRecord ar = (ActivityRecord) msg.obj;
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
- if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
- ar.packageName)) {
- // TODO(multi-display): Show dialog on appropriate display.
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- ActivityManagerService.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
- }
- }
- break;
- }
case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
@@ -2676,6 +2658,7 @@
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
+ mAppWarnings = null;
mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
@@ -2743,10 +2726,13 @@
mProviderMap = new ProviderMap(this);
mAppErrors = new AppErrors(mUiContext, this);
- // TODO: Move creation of battery stats service outside of activity manager service.
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
systemDir.mkdirs();
+
+ mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir);
+
+ // TODO: Move creation of battery stats service outside of activity manager service.
mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
mBatteryStatsService.getActiveStatistics().readLocked();
mBatteryStatsService.scheduleWriteToDisk();
@@ -2793,7 +2779,7 @@
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
mTaskChangeNotificationController =
new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
- mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+ mActivityStarter = new ActivityStarter(this);
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -3310,22 +3296,25 @@
mUiHandler.sendMessage(msg);
}
- final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- final Configuration globalConfig = getGlobalConfiguration();
- if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
- final Message msg = Message.obtain();
- msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
- msg.obj = r;
- mUiHandler.sendMessage(msg);
- }
+ final AppWarnings getAppWarningsLocked() {
+ return mAppWarnings;
+ }
+
+ /**
+ * Shows app warning dialogs, if necessary.
+ *
+ * @param r activity record for which the warnings may be displayed
+ */
+ final void showAppWarningsIfNeededLocked(ActivityRecord r) {
+ mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
+ mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
String what, Object obj, ProcessRecord srcApp) {
app.lastActivityTime = now;
- if (app.activities.size() > 0) {
+ if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
// Don't want to touch dependent processes that are hosting activities.
return index;
}
@@ -3389,7 +3378,7 @@
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
- || app.treatLikeActivity;
+ || app.treatLikeActivity || app.recentTasks.size() > 0;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activities, so we are only allowing activity-based adjustments
@@ -3493,7 +3482,8 @@
int nextIndex;
if (hasActivity) {
final int N = mLruProcesses.size();
- if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+ if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+ && mLruProcessActivityStart < (N - 1)) {
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
@@ -5330,6 +5320,8 @@
// Remove this application's activities from active lists.
boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
+ app.clearRecentTasks();
+
app.activities.clear();
if (app.instr != null) {
@@ -11715,6 +11707,15 @@
return true;
}
+ /**
+ * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+ * PackageManager could be unavailable at construction time and therefore needs to be accessed
+ * on demand.
+ */
+ IPackageManager getPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPackageManagerInt == null) {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -14566,6 +14567,18 @@
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+ // Log to StatsLog before the rate-limiting.
+ // The logging below is adapated from appendDropboxProcessHeaders.
+ StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED,
+ process != null ? process.uid : -1,
+ dropboxTag,
+ processName,
+ process != null ? process.pid : -1,
+ (process != null && process.info != null) ?
+ (process.info.isInstantApp() ? 1 : 0) : -1,
+ activity != null ? activity.shortComponentName : null,
+ process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
+
// Rate-limit how often we're willing to do the heavy lifting below to
// collect and record logs; currently 5 logs per 10 second period.
final long now = SystemClock.elapsedRealtime();
@@ -19350,13 +19363,7 @@
mRecentTasks.removeTasksByPackageName(ssp, userId);
mServices.forceStopPackageLocked(ssp, userId);
-
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
+ mAppWarnings.onPackageUninstalled(ssp);
mCompatModePackages.handlePackageUninstalledLocked(ssp);
mBatteryStatsService.notePackageUninstalled(ssp);
}
@@ -19435,13 +19442,8 @@
Uri data = intent.getData();
String ssp;
if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
- // Hide the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
- mUnsupportedDisplaySizeDialog.getPackageName())) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
- }
mCompatModePackages.handlePackageDataClearedLocked(ssp);
+ mAppWarnings.onPackageDataCleared(ssp);
}
break;
}
@@ -20631,8 +20633,7 @@
final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
if (isDensityChange && displayId == DEFAULT_DISPLAY) {
- // Reset the unsupported display size dialog.
- mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+ mAppWarnings.onDensityChanged();
killAllBackgroundProcessesExcept(N,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -21013,7 +21014,7 @@
+ " instead of expected " + app);
if (r.app == null || (r.app.uid == app.uid)) {
// Only fix things up when they look sane
- r.app = app;
+ r.setProcess(app);
} else {
continue;
}
@@ -21092,6 +21093,11 @@
adj += minLayer;
}
}
+ if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+ procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+ app.adjType = "cch-rec";
+ if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+ }
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22652,6 +22658,7 @@
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
@@ -23376,7 +23383,7 @@
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
- if (app.activities.size() == 0
+ if (app.activities.size() == 0 && app.recentTasks.size() == 0
&& app.curReceivers.isEmpty() && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9a16745..5927666 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -934,6 +934,14 @@
}
}
+ void setProcess(ProcessRecord proc) {
+ app = proc;
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root == this) {
+ task.setRootProcess(proc);
+ }
+ }
+
AppWindowContainerController getWindowContainerController() {
return mWindowContainerController;
}
@@ -2549,7 +2557,7 @@
}
results = null;
newIntents = null;
- service.showUnsupportedZoomDialogIfNeededLocked(this);
+ service.getAppWarningsLocked().onResumeActivity(this);
service.showAskCompatModeDialogLocked(this);
} else {
service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c086c52..6985d6e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2602,7 +2602,7 @@
next.shortComponentName);
next.sleeping = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(next);
+ mService.getAppWarningsLocked().onResumeActivity(next);
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(mService.mTopProcessState);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 745e9fb..ddde4bc 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1270,7 +1270,7 @@
// schedule launch ticks to collect information about slow apps.
r.startLaunchTickingLocked();
- r.app = app;
+ r.setProcess(app);
if (mKeyguardController.isKeyguardLocked()) {
r.notifyUnknownVisibilityLaunched();
@@ -1358,7 +1358,7 @@
PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
r.sleeping = false;
r.forceNewConfig = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(r);
+ mService.getAppWarningsLocked().onStartActivity(r);
mService.showAskCompatModeDialogLocked(r);
r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
ProfilerInfo profilerInfo = null;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 9b8cbc1..03162bb 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@
private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
- private final IPackageManager mPackageManager;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
@@ -234,9 +233,8 @@
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+ ActivityStarter(ActivityManagerService service) {
mService = service;
- mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
@@ -379,7 +377,7 @@
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
- if (!mPackageManager.activitySupportsIntent(
+ if (!mService.getPackageManager().activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@
// If the caller is starting a new voice session, just make sure the target
// is actually allowing it to run this way.
try {
- if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+ if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
intent, resolvedType)) {
Slog.w(TAG,
"Activity being started in new voice task does not support: "
diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
new file mode 100644
index 0000000..a3c0345
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppWarnings.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.UiThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AtomicFile;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages warning dialogs shown during application lifecycle.
+ */
+class AppWarnings {
+ private static final String TAG = "AppWarnings";
+ private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
+
+ public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
+ public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+
+ private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+
+ private final ActivityManagerService mAms;
+ private final Context mUiContext;
+ private final ConfigHandler mAmsHandler;
+ private final UiHandler mUiHandler;
+ private final AtomicFile mConfigFile;
+
+ private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
+ private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+
+ /**
+ * Creates a new warning dialog manager.
+ * <p>
+ * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
+ *
+ * @param ams
+ * @param uiContext
+ * @param amsHandler
+ * @param uiHandler
+ * @param systemDir
+ */
+ public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler,
+ Handler uiHandler, File systemDir) {
+ mAms = ams;
+ mUiContext = uiContext;
+ mAmsHandler = new ConfigHandler(amsHandler.getLooper());
+ mUiHandler = new UiHandler(uiHandler.getLooper());
+ mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME));
+
+ readConfigFromFileAmsThread();
+ }
+
+ /**
+ * Shows the "unsupported display size" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
+ final Configuration globalConfig = mAms.getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
+ mUiHandler.showUnsupportedDisplaySizeDialog(r);
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
+ if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
+ // We don't know enough about this package. Abort!
+ return;
+ }
+
+ // If the application was built against an pre-release SDK that's older than the current
+ // platform OR if the current platform is pre-release and older than the SDK against which
+ // the application was built OR both are pre-release with the same SDK_INT but different
+ // codenames (e.g. simultaneous pre-release development), then we're likely to run into
+ // compatibility issues. Warn the user and offer to check for an update.
+ final int compileSdk = r.appInfo.compileSdkVersion;
+ final int platformSdk = Build.VERSION.SDK_INT;
+ final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
+ final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
+ if ((isCompileSdkPreview && compileSdk < platformSdk)
+ || (isPlatformSdkPreview && platformSdk < compileSdk)
+ || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
+ && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
+ mUiHandler.showUnsupportedCompileSdkDialog(r);
+ }
+ }
+
+ /**
+ * Called when an activity is being started.
+ *
+ * @param r record for the activity being started
+ */
+ public void onStartActivity(ActivityRecord r) {
+ showUnsupportedCompileSdkDialogIfNeeded(r);
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called when an activity was previously started and is being resumed.
+ *
+ * @param r record for the activity being resumed
+ */
+ public void onResumeActivity(ActivityRecord r) {
+ showUnsupportedDisplaySizeDialogIfNeeded(r);
+ }
+
+ /**
+ * Called by ActivityManagerService when package data has been cleared.
+ *
+ * @param name the package whose data has been cleared
+ */
+ public void onPackageDataCleared(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when a package has been uninstalled.
+ *
+ * @param name the package that has been uninstalled
+ */
+ public void onPackageUninstalled(String name) {
+ removePackageAndHideDialogs(name);
+ }
+
+ /**
+ * Called by ActivityManagerService when the default display density has changed.
+ */
+ public void onDensityChanged() {
+ mUiHandler.hideUnsupportedDisplaySizeDialog();
+ }
+
+ /**
+ * Does what it says on the tin.
+ */
+ private void removePackageAndHideDialogs(String name) {
+ mUiHandler.hideDialogsForPackage(name);
+
+ synchronized (mPackageFlags) {
+ mPackageFlags.remove(name);
+ mAmsHandler.scheduleWrite();
+ }
+ }
+
+ /**
+ * Hides the "unsupported display size" warning.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ */
+ @UiThread
+ private void hideUnsupportedDisplaySizeDialogUiThread() {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ }
+
+ /**
+ * Shows the "unsupported display size" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedDisplaySizeDialog != null) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedDisplaySizeDialog.show();
+ }
+ }
+
+ /**
+ * Shows the "unsupported compile SDK" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
+ if (mUnsupportedCompileSdkDialog != null) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mUnsupportedCompileSdkDialog.show();
+ }
+ }
+
+ /**
+ * Dismisses all warnings for the given package.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
+ * all warnings
+ */
+ @UiThread
+ private void hideDialogsForPackageUiThread(String name) {
+ // Hides the "unsupported display" dialog if necessary.
+ if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ mUnsupportedDisplaySizeDialog.getPackageName()))) {
+ mUnsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialog = null;
+ }
+
+ // Hides the "unsupported compile SDK" dialog if necessary.
+ if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
+ mUnsupportedCompileSdkDialog.getPackageName()))) {
+ mUnsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialog = null;
+ }
+ }
+
+ /**
+ * Returns the value of the flag for the given package.
+ *
+ * @param name the package from which to retrieve the flag
+ * @param flag the bitmask for the flag to retrieve
+ * @return {@code true} if the flag is enabled, {@code false} otherwise
+ */
+ boolean hasPackageFlag(String name, int flag) {
+ return (getPackageFlags(name) & flag) == flag;
+ }
+
+ /**
+ * Sets the flag for the given package to the specified value.
+ *
+ * @param name the package on which to set the flag
+ * @param flag the bitmask for flag to set
+ * @param enabled the value to set for the flag
+ */
+ void setPackageFlag(String name, int flag, boolean enabled) {
+ synchronized (mPackageFlags) {
+ final int curFlags = getPackageFlags(name);
+ final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+ if (curFlags != newFlags) {
+ if (newFlags != 0) {
+ mPackageFlags.put(name, newFlags);
+ } else {
+ mPackageFlags.remove(name);
+ }
+ mAmsHandler.scheduleWrite();
+ }
+ }
+ }
+
+ /**
+ * Returns the bitmask of flags set for the specified package.
+ */
+ private int getPackageFlags(String name) {
+ synchronized (mPackageFlags) {
+ return mPackageFlags.getOrDefault(name, 0);
+ }
+ }
+
+ /**
+ * Handles messages on the system process UI thread.
+ */
+ private final class UiHandler extends Handler {
+ private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
+ private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
+ private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
+ private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedDisplaySizeDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+ hideUnsupportedDisplaySizeDialogUiThread();
+ } break;
+ case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showUnsupportedCompileSdkDialogUiThread(ar);
+ } break;
+ case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
+ final String name = (String) msg.obj;
+ hideDialogsForPackageUiThread(name);
+ } break;
+ }
+ }
+
+ public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
+ }
+
+ public void hideUnsupportedDisplaySizeDialog() {
+ removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+ }
+
+ public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
+ obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
+ }
+
+ public void hideDialogsForPackage(String name) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ }
+ }
+
+ /**
+ * Handles messages on the ActivityManagerService thread.
+ */
+ private final class ConfigHandler extends Handler {
+ private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+ private static final int DELAY_MSG_WRITE = 10000;
+
+ public ConfigHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_WRITE:
+ writeConfigToFileAmsThread();
+ break;
+ }
+ }
+
+ public void scheduleWrite() {
+ removeMessages(MSG_WRITE);
+ sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+ }
+ }
+
+ /**
+ * Writes the configuration file.
+ * <p>
+ * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
+ * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
+ */
+ private void writeConfigToFileAmsThread() {
+ // Create a shallow copy so that we don't have to synchronize on config.
+ final HashMap<String, Integer> packageFlags;
+ synchronized (mPackageFlags) {
+ packageFlags = new HashMap<>(mPackageFlags);
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = mConfigFile.startWrite();
+
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ out.startTag(null, "packages");
+
+ for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
+ String pkg = entry.getKey();
+ int mode = entry.getValue();
+ if (mode == 0) {
+ continue;
+ }
+ out.startTag(null, "package");
+ out.attribute(null, "name", pkg);
+ out.attribute(null, "flags", Integer.toString(mode));
+ out.endTag(null, "package");
+ }
+
+ out.endTag(null, "packages");
+ out.endDocument();
+
+ mConfigFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Slog.w(TAG, "Error writing package metadata", e1);
+ if (fos != null) {
+ mConfigFile.failWrite(fos);
+ }
+ }
+ }
+
+ /**
+ * Reads the configuration file and populates the package flags.
+ * <p>
+ * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * ActivityManagerService thread) since we don't synchronize on config.
+ */
+ private void readConfigFromFileAmsThread() {
+ FileInputStream fis = null;
+
+ try {
+ fis = mConfigFile.openRead();
+
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ return;
+ }
+
+ String tagName = parser.getName();
+ if ("packages".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2) {
+ if ("package".equals(tagName)) {
+ final String name = parser.getAttributeValue(null, "name");
+ if (name != null) {
+ final String flags = parser.getAttributeValue(
+ null, "flags");
+ int flagsInt = 0;
+ if (flags != null) {
+ try {
+ flagsInt = Integer.parseInt(flags);
+ } catch (NumberFormatException e) {
+ }
+ }
+ mPackageFlags.put(name, flagsInt);
+ }
+ }
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Error reading package metadata", e);
+ } catch (java.io.IOException e) {
+ if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index bfc0456..82019fd 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -60,6 +60,8 @@
public static final int COMPAT_FLAG_ENABLED = 1<<1;
// Unsupported zoom state: don't warn the user about unsupported zoom mode.
public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
+ // Unsupported compile SDK state: don't warn the user about unsupported compile SDK.
+ public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3;
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
@@ -237,6 +239,10 @@
return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
}
+ public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) {
+ return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0;
+ }
+
public void setFrontActivityAskCompatModeLocked(boolean ask) {
ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
if (r != null) {
@@ -245,22 +251,20 @@
}
public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
- int curFlags = getPackageFlags(packageName);
- int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
- if (curFlags != newFlags) {
- if (newFlags != 0) {
- mPackages.put(packageName, newFlags);
- } else {
- mPackages.remove(packageName);
- }
- scheduleWrite();
- }
+ setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
}
public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
+ setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify);
+ }
+
+ public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) {
+ setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify);
+ }
+
+ private void setPackageFlagLocked(String packageName, int flag, boolean set) {
final int curFlags = getPackageFlags(packageName);
- final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) :
- (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY);
+ final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
if (curFlags != newFlags) {
if (newFlags != 0) {
mPackages.put(packageName, newFlags);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7810c5e..1a19601 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -400,6 +400,9 @@
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
procState = "CACC";
break;
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ procState = "CRE ";
+ break;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
procState = "CEM ";
break;
@@ -494,6 +497,7 @@
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -515,6 +519,7 @@
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -536,6 +541,7 @@
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -557,6 +563,7 @@
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
@@ -578,6 +585,7 @@
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+ PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
};
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e847723..9d3c2ae 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@
// all activities running in the process
final ArrayList<ActivityRecord> activities = new ArrayList<>();
+ // any tasks this process had run root activities in
+ final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
// all ServiceRecord running in this process
final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@
pw.print(prefix); pw.print(" - "); pw.println(activities.get(i));
}
}
+ if (recentTasks.size() > 0) {
+ pw.print(prefix); pw.println("Recent Tasks:");
+ for (int i=0; i<recentTasks.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(recentTasks.get(i));
+ }
+ }
if (services.size() > 0) {
pw.print(prefix); pw.println("Services:");
for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@
}
}
+ public void clearRecentTasks() {
+ for (int i = recentTasks.size() - 1; i >= 0; i--) {
+ recentTasks.get(i).clearRootProcess();
+ }
+ recentTasks.clear();
+ }
+
/**
* This method returns true if any of the activities within the process record are interesting
* to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 949f51f..a9c6eee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -257,6 +257,11 @@
/** Current stack. Setter must always be used to update the value. */
private ActivityStack mStack;
+ /** The process that had previously hosted the root activity of this task.
+ * Used to know that we should try harder to keep this process around, in case the
+ * user wants to return to it. */
+ private ProcessRecord mRootProcess;
+
/** Takes on same value as first root activity */
boolean isPersistable = false;
int maxRecents;
@@ -962,6 +967,8 @@
mService.notifyTaskPersisterLocked(this, false);
}
+ clearRootProcess();
+
// TODO: Use window container controller once tasks are better synced between AM and WM
mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
}
@@ -2114,6 +2121,22 @@
}
}
+ void setRootProcess(ProcessRecord proc) {
+ clearRootProcess();
+ if (intent != null &&
+ (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+ mRootProcess = proc;
+ proc.recentTasks.add(this);
+ }
+ }
+
+ void clearRootProcess() {
+ if (mRootProcess != null) {
+ mRootProcess.recentTasks.remove(this);
+ mRootProcess = null;
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2221,9 @@
if (lastDescription != null) {
pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
}
+ if (mRootProcess != null) {
+ pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+ }
pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 0000000..600589a
--- /dev/null
+++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class UnsupportedCompileSdkDialog {
+ private final AlertDialog mDialog;
+ private final String mPackageName;
+
+ public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+ ApplicationInfo appInfo) {
+ mPackageName = appInfo.packageName;
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label = appInfo.loadSafeLabel(pm);
+ final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
+ label, appInfo.compileSdkVersionCodename);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setPositiveButton(R.string.ok, null)
+ .setMessage(message)
+ .setView(R.layout.unsupported_compile_sdk_dialog_content);
+
+ // If we might be able to update the app, show a button.
+ final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+ if (installerIntent != null) {
+ builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update,
+ (dialog, which) -> context.startActivity(installerIntent));
+ }
+
+ // Ensure the content view is prepared.
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+ // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+ window.getAttributes().setTitle("UnsupportedCompileSdkDialog");
+
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
+ alwaysShow.setChecked(true);
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void show() {
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ mDialog.dismiss();
+ }
+}
diff --git a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6b..8850663 100644
--- a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
@@ -30,7 +30,7 @@
private final AlertDialog mDialog;
private final String mPackageName;
- public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context,
+ public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
ApplicationInfo appInfo) {
mPackageName = appInfo.packageName;
@@ -54,14 +54,10 @@
// DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
window.getAttributes().setTitle("UnsupportedDisplaySizeDialog");
- final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox);
+ final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
- alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> {
- synchronized (service) {
- service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked(
- mPackageName, isChecked);
- }
- });
+ alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
public String getPackageName() {
diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.server.display;
+
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+ // Must be unique within the system server uid.
+ private static final int JOB_ID = 3923512;
+
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+ JobInfo jobInfo =
+ new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+ if (pending != null && !pending.equals(jobInfo)) {
+ jobScheduler.cancel(JOB_ID);
+ pending = null;
+ }
+
+ if (pending == null) {
+ jobScheduler.schedule(jobInfo);
+ }
+ }
+
+ public static void cancelJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ jobScheduler.cancel(JOB_ID);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (BrightnessTracker.DEBUG) {
+ Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+ }
+ DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+ dmi.persistBrightnessSliderEvents();
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 361d928..90888f0 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -61,12 +61,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -75,8 +75,8 @@
*/
public class BrightnessTracker {
- private static final String TAG = "BrightnessTracker";
- private static final boolean DEBUG = false;
+ static final String TAG = "BrightnessTracker";
+ static final boolean DEBUG = false;
private static final String EVENTS_FILE = "brightness_events.xml";
private static final int MAX_EVENTS = 100;
@@ -103,6 +103,8 @@
@GuardedBy("mEventsLock")
private RingBuffer<BrightnessChangeEvent> mEvents
= new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+ @GuardedBy("mEventsLock")
+ private boolean mEventsDirty;
private final Runnable mEventsWriter = () -> writeEvents();
private volatile boolean mWriteEventsScheduled;
@@ -170,6 +172,8 @@
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+ mInjector.scheduleIdleJob(mContext);
}
/** Stop listening for events */
@@ -181,6 +185,7 @@
mInjector.unregisterSensorListener(mContext, mSensorListener);
mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+ mInjector.cancelIdleJob(mContext);
}
/**
@@ -211,6 +216,10 @@
brightness, userId);
}
+ public void persistEvents() {
+ scheduleWriteEvents();
+ }
+
private void handleBrightnessChanged() {
if (DEBUG) {
Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
synchronized (mEventsLock) {
+ mEventsDirty = true;
mEvents.append(event);
}
}
@@ -291,8 +301,12 @@
private void writeEvents() {
mWriteEventsScheduled = false;
- // TODO kick off write on handler thread e.g. every 24 hours.
synchronized (mEventsLock) {
+ if (!mEventsDirty) {
+ // Nothing to write
+ return;
+ }
+
final AtomicFile writeTo = mInjector.getFile();
if (writeTo == null) {
return;
@@ -301,12 +315,14 @@
if (writeTo.exists()) {
writeTo.delete();
}
+ mEventsDirty = false;
} else {
FileOutputStream output = null;
try {
output = writeTo.startWrite();
writeEventsLocked(output);
writeTo.finishWrite(output);
+ mEventsDirty = false;
} catch (IOException e) {
writeTo.failWrite(output);
Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +333,8 @@
private void readEvents() {
synchronized (mEventsLock) {
+ // Read might prune events so mark as dirty.
+ mEventsDirty = true;
mEvents.clear();
final AtomicFile readFrom = mInjector.getFile();
if (readFrom != null && readFrom.exists()) {
@@ -344,13 +362,16 @@
out.startTag(null, TAG_EVENTS);
BrightnessChangeEvent[] toWrite = mEvents.toArray();
+ // Clear events, code below will add back the ones that are still within the time window.
+ mEvents.clear();
if (DEBUG) {
Slog.d(TAG, "Writing events " + toWrite.length);
}
- final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+ final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
for (int i = 0; i < toWrite.length; ++i) {
int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+ mEvents.append(toWrite[i]);
out.startTag(null, TAG_EVENT);
out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +486,17 @@
}
}
+ public void dump(PrintWriter pw) {
+ synchronized (mEventsLock) {
+ pw.println("BrightnessTracker state:");
+ pw.println(" mEvents.size=" + mEvents.size());
+ pw.println(" mEventsDirty=" + mEventsDirty);
+ }
+ synchronized (mDataCollectionLock) {
+ pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
+ }
+ }
+
// Not allowed to keep the SensorEvent so used to copy the data we care about.
private static class LightData {
public float lux;
@@ -635,5 +667,13 @@
public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
return ActivityManager.getService().getFocusedStackInfo();
}
+
+ public void scheduleIdleJob(Context context) {
+ BrightnessIdleJob.scheduleJob(context);
+ }
+
+ public void cancelIdleJob(Context context) {
+ BrightnessIdleJob.cancelJob(context);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f1e2011..7530f3e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1285,6 +1285,9 @@
pw.println();
mPersistentDataStore.dump(pw);
+
+ pw.println();
+ mBrightnessTracker.dump(pw);
}
}
@@ -1921,5 +1924,10 @@
public boolean isUidPresentOnDisplay(int uid, int displayId) {
return isUidPresentOnDisplayInternal(uid, displayId);
}
+
+ @Override
+ public void persistBrightnessSliderEvents() {
+ mBrightnessTracker.persistEvents();
+ }
}
}
diff --git a/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
new file mode 100644
index 0000000..7e7335d
--- /dev/null
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/services/core/java/com/android/server/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b9777ec..4a3becb 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -151,6 +151,7 @@
StorageController mStorageController;
/** Need directly for sending uid state changes */
private BackgroundJobsController mBackgroundJobsController;
+ private DeviceIdleJobsController mDeviceIdleJobsController;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -622,15 +623,24 @@
if (disabled) {
cancelJobsForUid(uid, "uid gone");
}
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+ }
}
@Override public void onUidActive(int uid) throws RemoteException {
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, true);
+ }
}
@Override public void onUidIdle(int uid, boolean disabled) {
if (disabled) {
cancelJobsForUid(uid, "app uid idle");
}
+ synchronized (mLock) {
+ mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+ }
}
@Override public void onUidCachedChanged(int uid, boolean cached) {
@@ -939,11 +949,11 @@
mControllers.add(mBatteryController);
mStorageController = StorageController.get(this);
mControllers.add(mStorageController);
- mBackgroundJobsController = BackgroundJobsController.get(this);
- mControllers.add(mBackgroundJobsController);
+ mControllers.add(BackgroundJobsController.get(this));
mControllers.add(AppIdleController.get(this));
mControllers.add(ContentObserverController.get(this));
- mControllers.add(DeviceIdleJobsController.get(this));
+ mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+ mControllers.add(mDeviceIdleJobsController);
// If the job store determined that it can't yet reschedule persisted jobs,
// we need to start watching the clock.
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 1af3b39..28b60e3 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -250,7 +250,7 @@
/**
* @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled by the provided user. Never null.
+ * @return A list of all the jobs scheduled for the provided user. Never null.
*/
public List<JobStatus> getJobsByUser(int userHandle) {
return mJobSet.getJobsByUser(userHandle);
@@ -287,6 +287,10 @@
mJobSet.forEachJob(uid, functor);
}
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ mJobSet.forEachJobForSourceUid(sourceUid, functor);
+ }
+
public interface JobStatusFunctor {
public void process(JobStatus jobStatus);
}
@@ -979,9 +983,12 @@
static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs;
+ // Same data but with the key as getSourceUid() of the jobs in each sheaf
+ private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
public JobSet() {
mJobs = new SparseArray<ArraySet<JobStatus>>();
+ mJobsPerSourceUid = new SparseArray<>();
}
public List<JobStatus> getJobsByUid(int uid) {
@@ -995,10 +1002,10 @@
// By user, not by uid, so we need to traverse by key and check
public List<JobStatus> getJobsByUser(int userId) {
- ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobs.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
- ArraySet<JobStatus> jobs = mJobs.valueAt(i);
+ final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+ for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
if (jobs != null) {
result.addAll(jobs);
}
@@ -1009,32 +1016,60 @@
public boolean add(JobStatus job) {
final int uid = job.getUid();
+ final int sourceUid = job.getSourceUid();
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs == null) {
jobs = new ArraySet<JobStatus>();
mJobs.put(uid, jobs);
}
- return jobs.add(job);
+ ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ if (jobsForSourceUid == null) {
+ jobsForSourceUid = new ArraySet<>();
+ mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
+ }
+ return jobs.add(job) && jobsForSourceUid.add(job);
}
public boolean remove(JobStatus job) {
final int uid = job.getUid();
- ArraySet<JobStatus> jobs = mJobs.get(uid);
- boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
- if (didRemove && jobs.size() == 0) {
- // no more jobs for this uid; let the now-empty set object be GC'd.
- mJobs.remove(uid);
+ final ArraySet<JobStatus> jobs = mJobs.get(uid);
+ final int sourceUid = job.getSourceUid();
+ final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+ boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
+ if (didRemove) {
+ if (jobs.size() == 0) {
+ // no more jobs for this uid; let the now-empty set object be GC'd.
+ mJobs.remove(uid);
+ }
+ if (jobsForSourceUid.size() == 0) {
+ mJobsPerSourceUid.remove(sourceUid);
+ }
+ return true;
}
- return didRemove;
+ return false;
}
- // Remove the jobs all users not specified by the whitelist of user ids
+ /**
+ * Removes the jobs of all users not specified by the whitelist of user ids.
+ * The jobs scheduled by non existent users will not be removed if they were
+ */
public void removeJobsOfNonUsers(int[] whitelist) {
- for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
- int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
- // check if job's user id is not in the whitelist
+ for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
if (!ArrayUtils.contains(whitelist, jobUserId)) {
- mJobs.removeAt(jobIndex);
+ mJobsPerSourceUid.removeAt(jobSetIndex);
+ }
+ }
+ for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+ final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
+ for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
+ final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
+ if (!ArrayUtils.contains(whitelist, jobUserId)) {
+ jobsForUid.removeAt(jobIndex);
+ }
+ }
+ if (jobsForUid.size() == 0) {
+ mJobs.removeAt(jobSetIndex);
}
}
}
@@ -1077,6 +1112,7 @@
public void clear() {
mJobs.clear();
+ mJobsPerSourceUid.clear();
}
public int size() {
@@ -1112,8 +1148,17 @@
}
}
- public void forEachJob(int uid, JobStatusFunctor functor) {
- ArraySet<JobStatus> jobs = mJobs.get(uid);
+ public void forEachJob(int callingUid, JobStatusFunctor functor) {
+ ArraySet<JobStatus> jobs = mJobs.get(callingUid);
+ if (jobs != null) {
+ for (int i = jobs.size() - 1; i >= 0; i--) {
+ functor.process(jobs.valueAt(i));
+ }
+ }
+ }
+
+ public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
if (jobs != null) {
for (int i = jobs.size() - 1; i >= 0; i--) {
functor.process(jobs.valueAt(i));
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43..b7eb9e0 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,14 +16,19 @@
package com.android.server.job.controllers;
+import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
@@ -42,11 +47,22 @@
private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false;
+ private static final long BACKGROUND_JOBS_DELAY = 3000;
+
+ static final int PROCESS_BACKGROUND_JOBS = 1;
// Singleton factory
private static Object sCreationLock = new Object();
private static DeviceIdleJobsController sController;
+ /**
+ * These are jobs added with a special flag to indicate that they should be exempted from doze
+ * when the app is temp whitelisted or in the foreground.
+ */
+ private final ArraySet<JobStatus> mAllowInIdleJobs;
+ private final SparseBooleanArray mForegroundUids;
+ private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
+ private final DeviceIdleJobsDelayHandler mHandler;
private final JobSchedulerService mJobSchedulerService;
private final PowerManager mPowerManager;
private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,14 +73,6 @@
private boolean mDeviceIdleMode;
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // These jobs were added when the app was in temp whitelist, these should be exempted from doze
- private final ArraySet<JobStatus> mTempWhitelistedJobs;
-
- final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
- @Override public void process(JobStatus jobStatus) {
- updateTaskStateLocked(jobStatus);
- }
- };
/**
* Returns a singleton for the DeviceIdleJobsController
@@ -108,8 +116,8 @@
+ Arrays.toString(mPowerSaveTempWhitelistAppIds));
}
boolean changed = false;
- for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
- changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+ for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
+ changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
}
if (changed) {
mStateChangedListener.onControllerStateChanged();
@@ -125,6 +133,7 @@
super(jobSchedulerService, context, lock);
mJobSchedulerService = jobSchedulerService;
+ mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
@@ -132,7 +141,9 @@
mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
mPowerSaveTempWhitelistAppIds =
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
- mTempWhitelistedJobs = new ArraySet<>();
+ mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
+ mAllowInIdleJobs = new ArraySet<>();
+ mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -150,7 +161,20 @@
}
mDeviceIdleMode = enabled;
if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
+ if (enabled) {
+ mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ } else {
+ // When coming out of doze, process all foreground uids immediately, while others
+ // will be processed after a delay of 3 seconds.
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+ mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
+ }
+ }
+ mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+ }
}
// Inform the job scheduler service about idle mode changes
if (changed) {
@@ -159,11 +183,30 @@
}
/**
+ * Called by jobscheduler service to report uid state changes between active and idle
+ */
+ public void setUidActiveLocked(int uid, boolean active) {
+ final boolean changed = (active != mForegroundUids.get(uid));
+ if (!changed) {
+ return;
+ }
+ if (LOG_DEBUG) {
+ Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+ }
+ mForegroundUids.put(uid, active);
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ /**
* Checks if the given job's scheduling app id exists in the device idle user whitelist.
*/
boolean isWhitelistedLocked(JobStatus job) {
- return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
- UserHandle.getAppId(job.getSourceUid()));
+ return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
+ UserHandle.getAppId(job.getSourceUid())) >= 0;
}
/**
@@ -175,31 +218,33 @@
}
private boolean updateTaskStateLocked(JobStatus task) {
- final boolean whitelisted = isWhitelistedLocked(task)
- || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
- final boolean enableTask = !mDeviceIdleMode || whitelisted;
+ final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+ && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
+ final boolean whitelisted = isWhitelistedLocked(task);
+ final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
}
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
- if (isTempWhitelistedLocked(jobStatus)) {
- mTempWhitelistedJobs.add(jobStatus);
- jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
- } else {
- updateTaskStateLocked(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.add(jobStatus);
}
+ updateTaskStateLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
- mTempWhitelistedJobs.remove(jobStatus);
+ if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+ mAllowInIdleJobs.remove(jobStatus);
+ }
}
@Override
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("DeviceIdleJobsController");
+ pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
@Override public void process(JobStatus jobStatus) {
if (!jobStatus.shouldDump(filterUid)) {
@@ -217,8 +262,42 @@
if (jobStatus.dozeWhitelisted) {
pw.print(" WHITELISTED");
}
+ if (mAllowInIdleJobs.contains(jobStatus)) {
+ pw.print(" ALLOWED_IN_DOZE");
+ }
pw.println();
}
});
}
+
+ final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+ boolean mChanged;
+
+ @Override
+ public void process(JobStatus jobStatus) {
+ mChanged |= updateTaskStateLocked(jobStatus);
+ }
+ }
+
+ final class DeviceIdleJobsDelayHandler extends Handler {
+ public DeviceIdleJobsDelayHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case PROCESS_BACKGROUND_JOBS:
+ // Just process all the jobs, the ones in foreground should already be running.
+ synchronized (mLock) {
+ mDeviceIdleUpdateFunctor.mChanged = false;
+ mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+ if (mDeviceIdleUpdateFunctor.mChanged) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+ break;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
new file mode 100644
index 0000000..7e7335d
--- /dev/null
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6ba1d8d..08da568 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,15 +16,21 @@
package com.android.server.notification;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_NULL;
import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService
+ .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
import static android.service.notification.NotificationListenerService
.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
@@ -32,12 +38,13 @@
.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService
+ .REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -48,14 +55,10 @@
import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.service.notification.NotificationListenerService.TRIM_FULL;
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -68,17 +71,17 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
-import android.app.NotificationChannelGroup;
-import android.app.backup.BackupManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Policy;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.backup.BackupManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
@@ -119,8 +122,8 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.Vibrator;
import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -174,9 +177,9 @@
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.notification.ManagedServices.UserProfiles;
import libcore.io.IoUtils;
@@ -196,7 +199,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -1520,7 +1522,11 @@
}
}
}
+ final NotificationChannel preUpdate =
+ mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+
mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
@@ -1533,12 +1539,40 @@
savePolicyFile();
}
+ private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
+ NotificationChannel update) {
+ try {
+ if ((preUpdate.getImportance() == IMPORTANCE_NONE
+ && update.getImportance() != IMPORTANCE_NONE)
+ || (preUpdate.getImportance() != IMPORTANCE_NONE
+ && update.getImportance() == IMPORTANCE_NONE)) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.getImportance() == IMPORTANCE_NONE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about channel change", e);
+ }
+ }
+
private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(pkg);
+
+ final NotificationChannelGroup preUpdate =
+ mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
+ if (!fromApp) {
+ maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
+ }
if (!fromListener) {
mListeners.notifyNotificationChannelGroupChanged(pkg,
UserHandle.of(UserHandle.getCallingUserId()), group,
@@ -1546,6 +1580,25 @@
}
}
+ private void maybeNotifyChannelGroupOwner(String pkg, int uid,
+ NotificationChannelGroup preUpdate, NotificationChannelGroup update) {
+ try {
+ if (preUpdate.isBlocked() != update.isBlocked()) {
+ getContext().sendBroadcastAsUser(
+ new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED)
+ .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+ update.getId())
+ .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+ update.isBlocked())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .setPackage(pkg),
+ UserHandle.of(UserHandle.getUserId(uid)), null);
+ }
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Can't notify app about group change", e);
+ }
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1924,11 +1977,18 @@
}
@Override
+ public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ return mRankingHelper.getNotificationChannelGroupWithChannels(
+ pkg, Binder.getCallingUid(), groupId, false);
+ }
+
+ @Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return new ParceledListSlice<>(new ArrayList(
- mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+ return mRankingHelper.getNotificationChannelGroups(
+ pkg, Binder.getCallingUid(), false, false);
}
@Override
@@ -1998,7 +2058,7 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+ return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index b9c0d90..b1b0bf2 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -36,7 +36,7 @@
void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromTargetApp);
ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted);
+ int uid, boolean includeDeleted, boolean includeNonGrouped);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index d566a45..c0dccb5 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -750,12 +750,15 @@
int uid) {
Preconditions.checkNotNull(pkg);
Record r = getRecord(pkg, uid);
+ if (r == null) {
+ return null;
+ }
return r.groups.get(groupId);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted) {
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
Preconditions.checkNotNull(pkg);
Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Record r = getRecord(pkg, uid);
@@ -783,7 +786,7 @@
}
}
}
- if (nonGrouped.getChannels().size() > 0) {
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
groups.put(null, nonGrouped);
}
return new ParceledListSlice<>(new ArrayList<>(groups.values()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bbe59eb..2e44e79 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3585,7 +3585,7 @@
final int N = list.size();
for (int i = 0; i < N; i++) {
ResolveInfo info = list.get(i);
- if (packageName.equals(info.activityInfo.packageName)) {
+ if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ee773a5..44f36d1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, params.sessionParams.abiOverride));
+ pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
} catch (PackageParserException | IOException e) {
getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@
}
List<String> failedPackages = new ArrayList<>();
+ int index = 0;
for (String packageName : packageNames) {
if (clearProfileData) {
mInterface.clearApplicationProfileData(packageName);
}
+ if (allPackages) {
+ pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+ pw.flush();
+ }
+
boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7748ae4..384070c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -304,6 +304,10 @@
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
+ static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4;
+
+ static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
+ static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
@@ -569,6 +573,7 @@
boolean mLidControlsSleep;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
int mLongPressOnBackBehavior;
@@ -586,6 +591,7 @@
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
+ int mVeryLongPressTimeout;
private boolean mHandleVolumeKeysInWM;
@@ -796,6 +802,7 @@
private static final int MSG_HANDLE_ALL_APPS = 26;
private static final int MSG_LAUNCH_ASSIST = 27;
private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
+ private static final int MSG_POWER_VERY_LONG_PRESS = 29;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -855,6 +862,9 @@
case MSG_POWER_LONG_PRESS:
powerLongPress();
break;
+ case MSG_POWER_VERY_LONG_PRESS:
+ powerVeryLongPress();
+ break;
case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
updateDreamingSleepToken(msg.arg1 != 0);
break;
@@ -1299,6 +1309,12 @@
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
}
} else {
wakeUpFromPowerKey(event.getDownTime());
@@ -1308,6 +1324,13 @@
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+ if (hasVeryLongPressOnPowerBehavior()) {
+ Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+ longMsg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+ }
+
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
@@ -1369,6 +1392,9 @@
mPowerKeyHandled = true;
mHandler.removeMessages(MSG_POWER_LONG_PRESS);
}
+ if (hasVeryLongPressOnPowerBehavior()) {
+ mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+ }
}
private void cancelPendingBackKeyAction() {
@@ -1516,6 +1542,29 @@
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
+ case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ final boolean keyguardActive = mKeyguardDelegate == null
+ ? false
+ : mKeyguardDelegate.isShowing();
+ if (!keyguardActive) {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+ }
+ break;
+ }
+ }
+
+ private void powerVeryLongPress() {
+ switch (mVeryLongPressOnPowerBehavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ break;
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ mPowerKeyHandled = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ showGlobalActionsInternal();
+ break;
}
}
@@ -1574,6 +1623,10 @@
return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
}
+ private boolean hasVeryLongPressOnPowerBehavior() {
+ return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING;
+ }
+
private boolean hasLongPressOnBackBehavior() {
return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
}
@@ -1979,12 +2032,16 @@
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_doublePressOnPowerBehavior);
mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mShortPressOnSleepBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+ mVeryLongPressTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
@@ -8193,6 +8250,9 @@
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mVeryLongPressOnPowerBehavior=");
+ pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
+ pw.print(prefix);
pw.print("mDoublePressOnPowerBehavior=");
pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
pw.print(prefix);
@@ -8445,6 +8505,18 @@
return Integer.toString(behavior);
}
}
+
+ private static String veryLongPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case VERY_LONG_PRESS_POWER_NOTHING:
+ return "VERY_LONG_PRESS_POWER_NOTHING";
+ case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
private static String multiPressOnPowerBehaviorToString(int behavior) {
switch (behavior) {
case MULTI_PRESS_POWER_NOTHING:
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 3992f8a..87c9274 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -32,6 +32,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.batterysaver.CpuFrequencies;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -40,15 +41,14 @@
/**
* Class to decide whether to turn on battery saver mode for specific service
*
- * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive}
- * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write
- * anything under /sys/ and /proc/ is too loose.
- *
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
*/
public class BatterySaverPolicy extends ContentObserver {
private static final String TAG = "BatterySaverPolicy";
+ public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
// Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
public static final int GPS_MODE_NO_CHANGE = 0;
// Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -64,18 +64,26 @@
private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
+ private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
- private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:";
- private static final String KEY_FILE_FOR_NONINTERACTIVE_PREFIX = "file-off:";
+ private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+ private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
- private static String mSettings;
- private static String mDeviceSpecificSettings;
- private static String mDeviceSpecificSettingsSource; // For dump() only.
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private String mSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettings;
+
+ @GuardedBy("mLock")
+ private String mDeviceSpecificSettingsSource; // For dump() only.
/**
* {@code true} if vibration is disabled in battery saver mode.
@@ -83,6 +91,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_VIBRATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mVibrationDisabled;
/**
@@ -91,6 +100,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ANIMATION_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAnimationDisabled;
/**
@@ -100,6 +110,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_SOUNDTRIGGER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mSoundTriggerDisabled;
/**
@@ -108,6 +119,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FULLBACKUP_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mFullBackupDeferred;
/**
@@ -116,6 +128,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_KEYVALUE_DEFERRED
*/
+ @GuardedBy("mLock")
private boolean mKeyValueBackupDeferred;
/**
@@ -124,6 +137,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_FIREWALL_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mFireWallDisabled;
/**
@@ -132,6 +146,7 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mAdjustBrightnessDisabled;
/**
@@ -140,14 +155,22 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_DATASAVER_DISABLED
*/
+ @GuardedBy("mLock")
private boolean mDataSaverDisabled;
/**
+ * {@code true} if launch boost should be disabled on battery saver.
+ */
+ @GuardedBy("mLock")
+ private boolean mLaunchBoostDisabled;
+
+ /**
* This is the flag to decide the gps mode in battery saver mode.
*
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_GPS_MODE
*/
+ @GuardedBy("mLock")
private int mGpsMode;
/**
@@ -157,20 +180,21 @@
* @see Settings.Global#BATTERY_SAVER_CONSTANTS
* @see #KEY_ADJUST_BRIGHTNESS_FACTOR
*/
+ @GuardedBy("mLock")
private float mAdjustBrightnessFactor;
/**
* Whether to put all apps in the stand-by mode.
*/
+ @GuardedBy("mLock")
private boolean mForceAllAppsStandby;
/**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
+ @GuardedBy("mLock")
private boolean mOptionalSensorsDisabled;
- private final Object mLock = new Object();
-
@GuardedBy("mLock")
private Context mContext;
@@ -227,7 +251,11 @@
@VisibleForTesting
String getGlobalSetting(String key) {
- return Settings.Global.getString(mContentResolver, key);
+ final ContentResolver cr;
+ synchronized (mLock) {
+ cr = mContentResolver;
+ }
+ return Settings.Global.getString(cr, key);
}
@VisibleForTesting
@@ -273,6 +301,11 @@
mSettings = setting;
mDeviceSpecificSettings = deviceSpecificSetting;
+ if (DEBUG) {
+ Slog.i(TAG, "mSettings=" + mSettings);
+ Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+ }
+
final KeyValueListParser parser = new KeyValueListParser(',');
// Non-device-specific parameters.
@@ -291,6 +324,7 @@
mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
+ mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
@@ -307,29 +341,11 @@
+ deviceSpecificSetting);
}
- mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX);
- mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_PREFIX);
- }
+ mFilesForInteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
- private static ArrayMap<String, String> collectParams(
- KeyValueListParser parser, String prefix) {
- final ArrayMap<String, String> ret = new ArrayMap<>();
-
- for (int i = parser.size() - 1; i >= 0; i--) {
- final String key = parser.keyAt(i);
- if (!key.startsWith(prefix)) {
- continue;
- }
- final String path = key.substring(prefix.length());
-
- if (!(path.startsWith("/sys/") || path.startsWith("/proc/"))) {
- Slog.wtf(TAG, "Invalid path: " + path);
- continue;
- }
-
- ret.put(path, parser.getString(key, ""));
- }
- return ret;
+ mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+ parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
}
/**
@@ -395,14 +411,20 @@
}
}
+ public boolean isLaunchBoostDisabled() {
+ synchronized (mLock) {
+ return mLaunchBoostDisabled;
+ }
+ }
+
public void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println();
pw.println("Battery saver policy");
- pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
- pw.println(" value: " + mSettings);
- pw.println(" Settings " + mDeviceSpecificSettingsSource);
- pw.println(" value: " + mDeviceSpecificSettings);
+ pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+ pw.println(" value: " + mSettings);
+ pw.println(" Settings: " + mDeviceSpecificSettingsSource);
+ pw.println(" value: " + mDeviceSpecificSettings);
pw.println();
pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -411,6 +433,7 @@
pw.println(" " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
pw.println(" " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
pw.println(" " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+ pw.println(" " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
new file mode 100644
index 0000000..b4300a9
--- /dev/null
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -0,0 +1,3 @@
+michaelwr@google.com
+
+per-file BatterySaverPolicy.java=omakoto@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 584761c..2c87a40 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1457,6 +1457,10 @@
case PowerManager.GO_TO_SLEEP_REASON_HDMI:
Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
break;
+ case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+ Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+ + uid +")...");
+ break;
default:
Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,16 @@
mIsVrModeEnabled = enabled;
}
- private static void powerHintInternal(int hintId, int data) {
+ private void powerHintInternal(int hintId, int data) {
+ // Maybe filter the event.
+ switch (hintId) {
+ case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+ if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+ return;
+ }
+ break;
+ }
+
nativeSendPowerHint(hintId, data);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 3db6a25..ae01ea57 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -49,7 +49,7 @@
public class BatterySaverController implements BatterySaverPolicyListener {
static final String TAG = "BatterySaverController";
- static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+ static final boolean DEBUG = BatterySaverPolicy.DEBUG;
private final Object mLock = new Object();
private final Context mContext;
@@ -174,6 +174,13 @@
}
/**
+ * @return true if launch boost should currently be disabled.
+ */
+ public boolean isLaunchBoostDisabled() {
+ return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+ }
+
+ /**
* Dispatch power save events to the listeners.
*
* This method is always called on the handler thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 0000000..1629486
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+ private static final String TAG = "CpuFrequencies";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+ public CpuFrequencies() {
+ }
+
+ /**
+ * Parse a string.
+ */
+ public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+ synchronized (mLock) {
+ mCoreAndFrequencies.clear();
+ try {
+ for (String pair : cpuNumberAndFrequencies.split("/")) {
+ final String[] coreAndFreq = pair.split(":", 2);
+
+ if (coreAndFreq.length != 2) {
+ throw new IllegalArgumentException("Wrong format");
+ }
+ final int core = Integer.parseInt(coreAndFreq[0]);
+ final long freq = Long.parseLong(coreAndFreq[1]);
+
+ mCoreAndFrequencies.put(core, freq);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Return a new map containing the filename-value pairs.
+ */
+ public ArrayMap<String, String> toSysFileMap() {
+ final ArrayMap<String, String> map = new ArrayMap<>();
+ addToSysFileMap(map);
+ return map;
+ }
+
+ /**
+ * Add the filename-value pairs to an existing map.
+ */
+ public void addToSysFileMap(Map<String, String> map) {
+ synchronized (mLock) {
+ final int size = mCoreAndFrequencies.size();
+
+ for (int i = 0; i < size; i++) {
+ final int core = mCoreAndFrequencies.keyAt(i);
+ final long freq = mCoreAndFrequencies.valueAt(i);
+
+ final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+ "/cpufreq/scaling_max_freq";
+
+ map.put(file, Long.toString(freq));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc4..cc1b540 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,259 @@
package com.android.server.power.batterysaver;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+
/**
* Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
*
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
*/
public class FileUpdater {
private static final String TAG = BatterySaverController.TAG;
private static final boolean DEBUG = BatterySaverController.DEBUG;
+ // Don't do disk access with this lock held.
private final Object mLock = new Object();
+
private final Context mContext;
+ private final Handler mHandler;
+
+ /**
+ * Filename -> value map that holds pending writes.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+ /**
+ * Filename -> value that holds the original value of each file.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+ /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+ @GuardedBy("mLock")
+ private int mRetries = 0;
+
+ private final int MAX_RETRIES;
+
+ private final long RETRY_INTERVAL_MS;
+
+ /**
+ * "Official" constructor. Don't use the other constructor in the production code.
+ */
public FileUpdater(Context context) {
- mContext = context;
+ this(context, IoThread.get().getLooper(), 10, 5000);
}
+ /**
+ * Constructor for test.
+ */
+ @VisibleForTesting
+ FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+ mContext = context;
+ mHandler = new Handler(looper);
+
+ MAX_RETRIES = maxRetries;
+ RETRY_INTERVAL_MS = retryIntervalMs;
+ }
+
+ /**
+ * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+ */
public void writeFiles(ArrayMap<String, String> fileValues) {
- if (DEBUG) {
- final int size = fileValues.size();
- for (int i = 0; i < size; i++) {
- Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
- + "' to '" + fileValues.keyAt(i) + "'");
+ synchronized (mLock) {
+ for (int i = fileValues.size() - 1; i >= 0; i--) {
+ final String file = fileValues.keyAt(i);
+ final String value = fileValues.valueAt(i);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+ }
+
+ mPendingWrites.put(file, value);
+
+ }
+ mRetries = 0;
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.post(mHandleWriteOnHandlerRunnable);
+ }
+ }
+
+ /**
+ * Restore the default values.
+ */
+ public void restoreDefault() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting file default values.");
+ }
+ mPendingWrites.clear();
+
+ writeFiles(mDefaultValues);
+ }
+ }
+
+ private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+ /** Convert map keys into a single string for debug messages. */
+ private String getKeysString(Map<String, String> source) {
+ return new ArrayList<>(source.keySet()).toString();
+ }
+
+ /** Clone an ArrayMap. */
+ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+ return new ArrayMap<>(source);
+ }
+
+ /**
+ * Called on the handler and writes {@link #mPendingWrites} to the disk.
+ *
+ * When it about to write to each file for the first time, it'll read the file and store
+ * the original value in {@link #mDefaultValues}.
+ */
+ private void handleWriteOnHandler() {
+ // We don't want to access the disk with the lock held, so copy the pending writes to
+ // a local map.
+ final ArrayMap<String, String> writes;
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+ getKeysString(mPendingWrites));
+ }
+
+ writes = cloneMap(mPendingWrites);
+ }
+
+ // Then write.
+
+ boolean needRetry = false;
+
+ final int size = writes.size();
+ for (int i = 0; i < size; i++) {
+ final String file = writes.keyAt(i);
+ final String value = writes.valueAt(i);
+
+ // Make sure the default value is loaded.
+ if (!ensureDefaultLoaded(file)) {
+ continue;
+ }
+
+ // Write to the file. When succeeded, remove it from the pending list.
+ // Otherwise, schedule a retry.
+ try {
+ injectWriteToFile(file, value);
+
+ removePendingWrite(file);
+ } catch (IOException e) {
+ needRetry = true;
}
}
+ if (needRetry) {
+ scheduleRetry();
+ }
}
- public void restoreDefault() {
- if (DEBUG) {
- Slog.d(TAG, "Resetting file default values");
+ private void removePendingWrite(String file) {
+ synchronized (mLock) {
+ mPendingWrites.remove(file);
}
}
+
+ private void scheduleRetry() {
+ synchronized (mLock) {
+ if (mPendingWrites.size() == 0) {
+ return; // Shouldn't happen but just in case.
+ }
+
+ mRetries++;
+ if (mRetries > MAX_RETRIES) {
+ doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+ return;
+ }
+
+ mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+ mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+ }
+ }
+
+ /**
+ * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+ *
+ * @return true if the default value is loaded. false if the file cannot be read.
+ */
+ private boolean ensureDefaultLoaded(String file) {
+ // Has the default already?
+ synchronized (mLock) {
+ if (mDefaultValues.containsKey(file)) {
+ return true;
+ }
+ }
+ final String originalValue;
+ try {
+ originalValue = injectReadFromFileTrimmed(file);
+ } catch (IOException e) {
+ // If the file is not readable, assume can't write too.
+ injectWtf("Unable to read from file", e);
+
+ removePendingWrite(file);
+ return false;
+ }
+ synchronized (mLock) {
+ mDefaultValues.put(file, originalValue);
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return IoUtils.readFileAsString(file).trim();
+ }
+
+ @VisibleForTesting
+ void injectWriteToFile(String file, String value) throws IOException {
+ if (DEBUG) {
+ Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+ }
+ try (FileWriter out = new FileWriter(file)) {
+ out.write(value);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+ throw e;
+ }
+ }
+
+ private void doWtf(String message) {
+ injectWtf(message, null);
+ }
+
+ @VisibleForTesting
+ void injectWtf(String message, Throwable e) {
+ Slog.wtf(TAG, message, e);
+ }
}
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
new file mode 100644
index 0000000..09136dc
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -0,0 +1 @@
+omakoto@google.com
diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 0000000..af7ff41
--- /dev/null
+++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+public class AppInstallerUtil {
+ private static final String LOG_TAG = "AppInstallerUtil";
+
+ private static Intent resolveIntent(Context context, Intent i) {
+ ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
+ return result != null ? new Intent(i.getAction())
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
+ }
+
+ /**
+ * Returns the package name of the app which installed a given packageName, if available.
+ */
+ public static String getInstallerPackageName(Context context, String packageName) {
+ String installerPackageName = null;
+ try {
+ installerPackageName =
+ context.getPackageManager().getInstallerPackageName(packageName);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+ }
+ if (installerPackageName == null) {
+ return null;
+ }
+ return installerPackageName;
+ }
+
+ /**
+ * Returns an intent to launcher the installer for a given package name.
+ */
+ public static Intent createIntent(Context context, String installerPackageName,
+ String packageName) {
+ Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName);
+ final Intent result = resolveIntent(context, intent);
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ return result;
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method that looks up the installerPackageName.
+ */
+ public static Intent createIntent(Context context, String packageName) {
+ String installerPackageName = getInstallerPackageName(context, packageName);
+ return createIntent(context, installerPackageName, packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4656539..e123bef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7455,6 +7455,11 @@
public void registerDragDropControllerCallback(IDragDropCallback callback) {
mDragDropController.registerCallback(callback);
}
+
+ @Override
+ public void lockNow() {
+ WindowManagerService.this.lockNow(null);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 52b7a25..730ec37 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2109,7 +2109,11 @@
if (task != null) {
return task.getDimmer();
}
- return getStack().getDimmer();
+ TaskStack taskStack = getStack();
+ if (taskStack != null) {
+ return taskStack.getDimmer();
+ }
+ return null;
}
/** Returns true if the replacement window was removed. */
@@ -4390,10 +4394,7 @@
return mToken.makeChildSurface(this);
}
-
- @Override
- void prepareSurfaces() {
- mIsDimming = false;
+ private void applyDims(Dimmer dimmer) {
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
@@ -4402,6 +4403,15 @@
mIsDimming = true;
getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
}
+ }
+
+ @Override
+ void prepareSurfaces() {
+ final Dimmer dimmer = getDimmer();
+ mIsDimming = false;
+ if (dimmer != null) {
+ applyDims(dimmer);
+ }
mWinAnimator.prepareSurfaceLocked(true);
super.prepareSurfaces();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee..60c36d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4917,7 +4917,8 @@
@Override
public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
- byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ byte[] cert, byte[] chain, String alias, boolean requestAccess,
+ boolean isUserSelectable) {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -4935,6 +4936,7 @@
if (requestAccess) {
keyChain.setGrant(callingUid, alias, true);
}
+ keyChain.setUserSelectable(alias, isUserSelectable);
return true;
} catch (RemoteException e) {
Log.e(LOG_TAG, "Installing certificate", e);
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 0000000..54d233a
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 23)
+@Presubmit
+public class TransportClientTest {
+ private static final String PACKAGE_NAME = "some.package.name";
+ private static final ComponentName TRANSPORT_COMPONENT =
+ new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+
+ @Mock private Context mContext;
+ @Mock private TransportConnectionListener mTransportConnectionListener;
+ @Mock private TransportConnectionListener mTransportConnectionListener2;
+ @Mock private IBackupTransport.Stub mIBackupTransport;
+ private TransportClient mTransportClient;
+ private Intent mBindIntent;
+ private ShadowLooper mShadowLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Looper mainLooper = Looper.getMainLooper();
+ mShadowLooper = shadowOf(mainLooper);
+ mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
+ mTransportClient =
+ new TransportClient(
+ mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(true);
+ }
+
+ // TODO: Testing implementation? Remove?
+ @Test
+ public void testConnectAsync_callsBindService() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ verify(mContext)
+ .bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void testConnectAsync_callsListenerWhenConnected() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ // Simulate framework connecting
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+ when(mContext.bindServiceAsUser(
+ eq(mBindIntent),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class)))
+ .thenReturn(false);
+
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ verify(mContext).unbindService(eq(connection));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+ connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+ connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+ // Yes, it should return null because the object became unusable, check design doc
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
+
+ // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
+ /*@Test
+ public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller");
+
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ }
+
+ @Test
+ public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
+ throws Exception {
+ mTransportClient.connectAsync(mTransportListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+ mTransportClient.connectAsync(mTransportListener2, "caller2");
+
+ connection.onBindingDied(TRANSPORT_COMPONENT);
+
+ mShadowLooper.runToEndOfTasks();
+ verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
+ }*/
+
+ private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
+ ArgumentCaptor<ServiceConnection> connectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(context)
+ .bindServiceAsUser(
+ any(Intent.class),
+ connectionCaptor.capture(),
+ anyInt(),
+ any(UserHandle.class));
+ return connectionCaptor.getValue();
+ }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index c145e82..55ec133 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,8 +16,10 @@
package com.android.server.notification;
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -940,6 +942,120 @@
}
@Test
+ public void testUpdateChannelNotifyCreatorBlock() throws Exception {
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ NotificationChannel updatedChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, updatedChannel);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateChannelNotifyCreatorUnblock() throws Exception {
+ NotificationChannel existingChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(existingChannel);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateChannelNoNotifyCreatorOtherChanges() throws Exception {
+ NotificationChannel existingChannel =
+ new NotificationChannel(mTestNotificationChannel.getId(),
+ mTestNotificationChannel.getName(), IMPORTANCE_MAX);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(existingChannel);
+
+ mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+ verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+ }
+
+ @Test
+ public void testUpdateGroupNotifyCreatorBlock() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
+ updated.setBlocked(true);
+
+ mBinderService.updateNotificationChannelGroupForPackage(PKG, 0, updated);
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(existing.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ existing.setBlocked(true);
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ mBinderService.updateNotificationChannelGroupForPackage(
+ PKG, 0, new NotificationChannelGroup("id", "name"));
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+ assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+ captor.getValue().getAction());
+ assertEquals(PKG, captor.getValue().getPackage());
+ assertEquals(existing.getId(), captor.getValue().getStringExtra(
+ NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+ assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+ }
+
+ @Test
+ public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
+ NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+ mService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ .thenReturn(existing);
+
+ mBinderService.updateNotificationChannelGroupForPackage(
+ PKG, 0, new NotificationChannelGroup("id", "new name"));
+ verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+ }
+
+ @Test
public void testCreateChannelNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -1040,6 +1156,9 @@
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+ when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, PKG, Process.myUserHandle(), mTestNotificationChannel);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 3c02e23..2d03f11 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -372,7 +372,7 @@
mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -442,7 +442,7 @@
mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -1281,7 +1281,8 @@
mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
+ assertEquals(0,
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
}
@Test
@@ -1370,7 +1371,7 @@
mHelper.createNotificationChannel(PKG, UID, channel3, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -1402,13 +1403,13 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
channel1.setImportance(IMPORTANCE_LOW);
mHelper.updateNotificationChannel(PKG, UID, channel1, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 3ac56bb..57da0af 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -30,6 +30,6 @@
<string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string>
<string name="config_batterySaverDeviceSpecificConfig_1"></string>
- <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string>
- <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string>
+ <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
+ <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
</resources>
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index f9933fb6..bc503c4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -95,8 +95,7 @@
public void setUp() throws Exception {
super.setUp();
mService = createActivityManagerService();
- mPackageManager = mock(IPackageManager.class);
- mStarter = new ActivityStarter(mService, mPackageManager);
+ mStarter = new ActivityStarter(mService);
}
@Test
@@ -178,7 +177,7 @@
int expectedResult) {
final ActivityManagerService service = createActivityManagerService();
final IPackageManager packageManager = mock(IPackageManager.class);
- final ActivityStarter starter = new ActivityStarter(service, packageManager);
+ final ActivityStarter starter = new ActivityStarter(service);
final IApplicationThread caller = mock(IApplicationThread.class);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 9c949ad..9683e22 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -36,6 +36,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -90,6 +91,7 @@
protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
service = spy(service);
+ doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
service.mWindowManager = prepareMockWindowManager();
return service;
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index d9fac87..6938e0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -99,10 +99,12 @@
assertNotNull(mInjector.mSensorListener);
assertNotNull(mInjector.mSettingsObserver);
assertNotNull(mInjector.mBroadcastReceiver);
+ assertTrue(mInjector.mIdleScheduled);
mTracker.stop();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mSettingsObserver);
assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
}
@Test
@@ -399,6 +401,52 @@
}
@Test
+ public void testWritePrunesOldEvents() throws Exception {
+ final int brightness = 20;
+
+ mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
+ mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+ mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+ startTracker(mTracker);
+ mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+ batteryChangeEvent(30, 100));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+ mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
+ final long sensorTime = mInjector.currentTimeMillis();
+ mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS));
+
+ // 31 days later
+ mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
+ mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
+ mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS));
+ final long eventTime = mInjector.currentTimeMillis();
+
+ List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList();
+ assertEquals(2, events.size());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mTracker.writeEventsLocked(baos);
+ events = mTracker.getEvents(0).getList();
+ mTracker.stop();
+
+ assertEquals(1, events.size());
+ BrightnessChangeEvent event = events.get(0);
+ assertEquals(eventTime, event.timeStamp);
+
+ // We will keep one of the old sensor events because we keep 1 event outside the window.
+ assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+ assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
+ assertEquals(brightness, event.brightness);
+ assertEquals(0.3, event.batteryLevel, 0.01f);
+ assertTrue(event.nightMode);
+ assertEquals(3339, event.colorTemperature);
+ }
+
+ @Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
BrightnessChangeEvent event = new BrightnessChangeEvent();
@@ -516,6 +564,7 @@
long mCurrentTimeMillis = System.currentTimeMillis();
long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
Handler mHandler;
+ boolean mIdleScheduled;
public TestInjector(Handler handler) {
mHandler = handler;
@@ -636,5 +685,14 @@
focusedStack.topActivity = new ComponentName("a.package", "a.class");
return focusedStack;
}
+
+ public void scheduleIdleJob(Context context) {
+ // Don't actually schedule jobs during unit tests.
+ mIdleScheduled = true;
+ }
+
+ public void cancelIdleJob(Context context) {
+ mIdleScheduled = false;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index fa8feb0..1f9a243 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -197,6 +197,8 @@
assertEquals(a.coreApp, b.coreApp);
assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers);
assertEquals(a.mTrustedOverlay, b.mTrustedOverlay);
+ assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion);
+ assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename);
assertEquals(a.use32bitAbi, b.use32bitAbi);
assertEquals(a.packageName, b.packageName);
assertTrue(Arrays.equals(a.splitNames, b.splitNames));
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 0db19e4..20cf733 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -237,21 +237,27 @@
mBatterySaverPolicy.onChange();
assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString())
- .isEqualTo("{/sys/a=1, /sys/b=2}");
-
+ .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
+ "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
mBatterySaverPolicy.onChange();
- assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/c=4}");
- assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}");
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
+ "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+ assertThat(mBatterySaverPolicy.getFileValues(false).toString())
+ .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
- "file-on:/proc/z=4");
+ "cpufreq-i=3:1234567890/4:014/5:015");
mBatterySaverPolicy.onChange();
- assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}");
+ assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+ .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
+ "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
+ "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
new file mode 100644
index 0000000..f72ec34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.power.batterysaver;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CpuFrequenciesTest {
+ private void check(ArrayMap<String, String> expected, String config) {
+ assertEquals(expected, (new CpuFrequencies().parseString(config))
+ .toSysFileMap());
+ }
+
+ @Test
+ public void test() {
+ check(new ArrayMap<>(), "");
+
+ final ArrayMap<String, String> expected = new ArrayMap<>();
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+ check(expected, "0:0");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+ expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1");
+ check(expected, "0:0/1:1");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", "0");
+ expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1234567890");
+ check(expected, "2:0/1:1234567890");
+
+ expected.clear();
+ expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "1900800");
+ expected.put("/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq", "1958400");
+ check(expected, "0:1900800/4:1958400");
+
+ check(expected, "0:1900800/4:1958400/"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/a:1"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1:"); // Shouldn't crash.
+ check(expected, "0:1900800/4:1958400/1:b"); // Shouldn't crash.
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
new file mode 100644
index 0000000..7e2a7d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.server.power.batterysaver;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FileUpdaterTest {
+
+ private class FileUpdaterTestable extends FileUpdater {
+ FileUpdaterTestable(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+ super(context, looper, maxRetries, retryIntervalMs);
+ }
+
+ @Override
+ String injectReadFromFileTrimmed(String file) throws IOException {
+ return mInjector.injectReadFromFileTrimmed(file);
+ }
+
+ @Override
+ void injectWriteToFile(String file, String value) throws IOException {
+ mInjector.injectWriteToFile(file, value);
+ }
+
+ @Override
+ void injectWtf(String message, Throwable e) {
+ mInjector.injectWtf(message, e);
+ }
+ }
+
+ private interface Injector {
+ String injectReadFromFileTrimmed(String file) throws IOException;
+ void injectWriteToFile(String file, String value) throws IOException;
+ void injectWtf(String message, Throwable e);
+ }
+
+ private Handler mMainHandler;
+
+ @Mock
+ private Injector mInjector;
+
+ private static final int MAX_RETRIES = 3;
+
+ private FileUpdaterTestable mInstance;
+
+ public static <T> T anyOrNull(Class<T> clazz) {
+ return ArgumentMatchers.argThat(value -> true);
+ }
+
+ public static String anyOrNullString() {
+ return ArgumentMatchers.argThat(value -> true);
+ }
+
+ @Before
+ public void setUp() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+
+ MockitoAnnotations.initMocks(this);
+
+ mInstance = newInstance();
+ }
+
+ private FileUpdaterTestable newInstance() {
+ return new FileUpdaterTestable(
+ InstrumentationRegistry.getContext(),
+ Looper.getMainLooper(),
+ MAX_RETRIES,
+ 0 /* retry with no delays*/);
+ }
+
+ private void waitUntilMainHandlerDrain() throws Exception {
+ final CountDownLatch l = new CountDownLatch(1);
+ mMainHandler.post(() -> l.countDown());
+ assertTrue(l.await(5, TimeUnit.SECONDS));
+ }
+
+ private void veriryWtf(int times) {
+ verify(mInjector, times(times)).injectWtf(anyOrNullString(), anyOrNull(Throwable.class));
+ }
+
+ @Test
+ public void testNoWrites() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testSimpleWrite() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testMultiWrites() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("file3", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+ verify(mInjector, times(1)).injectWriteToFile("file3", "33");
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+ verify(mInjector, times(1)).injectWriteToFile("file3", "333");
+
+ // No WTF should have happened.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testCantReadDefault() throws Exception {
+ doThrow(new IOException("can't read")).when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ veriryWtf(1);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(0)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+ veriryWtf(1);
+ }
+
+ @Test
+ public void testWriteGiveUp() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+ doThrow(new IOException("can't write")).when(mInjector).injectWriteToFile(
+ eq("fail1"), eq("33"));
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("fail1", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ verify(mInjector, times(MAX_RETRIES + 1)).injectWriteToFile("fail1", "33");
+
+ // 1 WTF.
+ veriryWtf(1);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+ verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+ // No further WTF.
+ veriryWtf(1);
+ }
+
+ @Test
+ public void testSuccessWithRetry() throws Exception {
+ doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+ doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+ doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+ final AtomicInteger counter = new AtomicInteger();
+ doAnswer((inv) -> {
+ if (counter.getAndIncrement() <= 1) {
+ throw new IOException();
+ }
+ return null;
+ }).when(mInjector).injectWriteToFile(eq("fail1"), eq("33"));
+
+ // Write
+ final ArrayMap<String, String> values = new ArrayMap<>();
+ values.put("file1", "11");
+ values.put("file2", "22");
+ values.put("fail1", "33");
+
+ mInstance.writeFiles(values);
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+ // Should succeed after 2 retries.
+ verify(mInjector, times(3)).injectWriteToFile("fail1", "33");
+
+ // No WTF.
+ veriryWtf(0);
+
+ // Reset to default
+ mInstance.restoreDefault();
+ waitUntilMainHandlerDrain();
+
+ verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+ verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+ verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+ // Still no WTF.
+ veriryWtf(0);
+ }
+
+ @Test
+ public void testAll() throws Exception {
+ // Run multiple tests on the single target instance.
+
+ reset(mInjector);
+ testSimpleWrite();
+
+ reset(mInjector);
+ testWriteGiveUp();
+
+ reset(mInjector);
+ testMultiWrites();
+
+ reset(mInjector);
+ testSuccessWithRetry();
+
+ reset(mInjector);
+ testMultiWrites();
+ }
+}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
index 011817e..884ba70 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
@@ -27,12 +27,11 @@
public class TestJobActivity extends Activity {
private static final String TAG = TestJobActivity.class.getSimpleName();
- public static final String EXTRA_JOB_ID_KEY =
- "com.android.servicestests.apps.jobtestapp.extra.JOB_ID";
- public static final String ACTION_START_JOB =
- "com.android.servicestests.apps.jobtestapp.action.START_JOB";
- public static final String ACTION_CANCEL_JOBS =
- "com.android.servicestests.apps.jobtestapp.action.CANCEL_JOBS";
+ private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+
+ public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
+ public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
+ public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
public static final int JOB_INITIAL_BACKOFF = 10_000;
public static final int JOB_MINIMUM_LATENCY = 5_000;
@@ -59,6 +58,8 @@
Log.d(TAG, "Successfully scheduled job with id " + jobId);
}
break;
+ default:
+ Log.e(TAG, "Unknown action " + intent.getAction());
}
finish();
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 095fdc6..9bc9cd0 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -323,7 +323,8 @@
}
/* Opens the specified USB device */
- public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+ public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings,
+ String packageName, int uid) {
synchronized (mLock) {
if (isBlackListed(deviceName)) {
throw new SecurityException("USB device is on a restricted bus");
@@ -334,7 +335,7 @@
throw new IllegalArgumentException(
"device " + deviceName + " does not exist or is restricted");
}
- settings.checkPermission(device);
+ settings.checkPermission(device, packageName, uid);
return nativeOpenDevice(deviceName);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index e4fcea7..17de83f 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -232,7 +232,7 @@
/* Opens the specified USB device (host mode) */
@Override
- public ParcelFileDescriptor openDevice(String deviceName) {
+ public ParcelFileDescriptor openDevice(String deviceName, String packageName) {
ParcelFileDescriptor fd = null;
if (mHostManager != null) {
@@ -242,7 +242,8 @@
boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
if (isCurrentUser) {
- fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+ fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt),
+ packageName, Binder.getCallingUid());
} else {
Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
" as user is not active.");
@@ -308,9 +309,10 @@
}
@Override
- public boolean hasDevicePermission(UsbDevice device) {
+ public boolean hasDevicePermission(UsbDevice device, String packageName) {
final int userId = UserHandle.getCallingUserId();
- return getSettingsForUser(userId).hasPermission(device);
+ return getSettingsForUser(userId).hasPermission(device, packageName,
+ Binder.getCallingUid());
}
@Override
@@ -322,7 +324,8 @@
@Override
public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
final int userId = UserHandle.getCallingUserId();
- getSettingsForUser(userId).requestPermission(device, packageName, pi);
+ getSettingsForUser(userId).requestPermission(device, packageName, pi,
+ Binder.getCallingUid());
}
@Override
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211..11e43e3 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -26,6 +26,8 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbManager;
import android.os.Binder;
import android.os.Process;
@@ -95,10 +97,70 @@
}
}
+ /**
+ * Check whether a particular device or any of its interfaces
+ * is of class VIDEO.
+ *
+ * @param device The device that needs to get scanned
+ * @return True in case a VIDEO device or interface is present,
+ * False otherwise.
+ */
+ private boolean isCameraDevicePresent(UsbDevice device) {
+ if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
- public boolean hasPermission(UsbDevice device) {
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ UsbInterface iface = device.getInterface(i);
+ if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for camera permission of the calling process.
+ *
+ * @param packageName Package name of the caller.
+ * @param uid Linux uid of the calling process.
+ *
+ * @return True in case camera permission is available, False otherwise.
+ */
+ private boolean isCameraPermissionGranted(String packageName, int uid) {
+ int targetSdkVersion = android.os.Build.VERSION_CODES.P;
+ try {
+ ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ // compare uid with packageName to foil apps pretending to be someone else
+ if (aInfo.uid != uid) {
+ Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
+ return false;
+ }
+ targetSdkVersion = aInfo.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Package not found, likely due to invalid package name!");
+ return false;
+ }
+
+ if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
+ int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+ if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
+ Slog.i(TAG, "Camera permission required for USB video class devices");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean hasPermission(UsbDevice device, String packageName, int uid) {
synchronized (mLock) {
- int uid = Binder.getCallingUid();
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ return false;
+ }
+ }
if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
return true;
}
@@ -124,8 +186,8 @@
}
}
- public void checkPermission(UsbDevice device) {
- if (!hasPermission(device)) {
+ public void checkPermission(UsbDevice device, String packageName, int uid) {
+ if (!hasPermission(device, packageName, uid)) {
throw new SecurityException("User has not given permission to device " + device);
}
}
@@ -166,11 +228,11 @@
}
}
- public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+ public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
Intent intent = new Intent();
// respond immediately if permission has already been granted
- if (hasPermission(device)) {
+ if (hasPermission(device, packageName, uid)) {
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
try {
@@ -180,6 +242,18 @@
}
return;
}
+ if (isCameraDevicePresent(device)) {
+ if (!isCameraPermissionGranted(packageName, uid)) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+ try {
+ pi.send(mUserContext, 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+ }
+ return;
+ }
+ }
// start UsbPermissionActivity so user can choose an activity
intent.putExtra(UsbManager.EXTRA_DEVICE, device);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4ffb3c3..e561e01 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -53,8 +53,9 @@
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
@@ -4659,27 +4660,78 @@
public @interface Feature {}
/**
- * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
- * feature or {@link null} if the service is not available. If an ImsServiceController is
- * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
- * feature updates.
- * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for.
- * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+ * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
+ * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
* @param callback Listener that will send updates to ImsManager when there are updates to
* ImsServiceController.
- * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
+ * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if
* it is unavailable.
* @hide
*/
- public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature,
- IImsServiceFeatureListener callback) {
+ public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback);
+ return telephony.getMMTelFeatureAndListen(slotIndex, callback);
}
} catch (RemoteException e) {
- Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+ Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+ * feature for emergency calling or {@link null} if the service is not available. If an
+ * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+ * listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: "
+ + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
+ * feature for emergency calling or {@link null} if the service is not available. If an
+ * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+ * listener for feature updates.
+ * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
+ * it is unavailable.
+ * @hide
+ */
+ public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
+ IImsServiceFeatureCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getRcsFeatureAndListen(slotIndex, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
+ + e.getMessage());
}
return null;
}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 9d91cc3..8230eaf 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,13 +16,11 @@
package android.telephony.ims;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.IBinder;
-import android.os.Message;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.feature.ImsFeature;
@@ -31,22 +29,13 @@
import android.util.Log;
import android.util.SparseArray;
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
-import com.android.ims.internal.IImsUt;
import com.android.internal.annotations.VisibleForTesting;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
-import static android.Manifest.permission.READ_PHONE_STATE;
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
@@ -92,247 +81,38 @@
*/
public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService";
- // A map of slot Id -> Set of features corresponding to that slot.
- private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>();
+ // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that
+ // slot.
+ // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and
+ // call ImsFeature#onFeatureRemoved.
+ private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>();
/**
* @hide
*/
- // Implements all supported features as a flat interface.
protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
@Override
- public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
+ public IImsMMTelFeature createEmergencyMMTelFeature(int slotId,
+ IImsFeatureStatusCallback c) {
+ return createEmergencyMMTelFeatureInternal(slotId, c);
+ }
+
+ @Override
+ public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) {
+ return createMMTelFeatureInternal(slotId, c);
+ }
+
+ @Override
+ public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
+ return createRcsFeatureInternal(slotId, c);
+ }
+
+ @Override
+ public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature");
- onCreateImsFeatureInternal(slotId, feature, c);
- }
+ ImsService.this.removeImsFeature(slotId, featureType, c);
}
-
- @Override
- public void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
- throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
- onRemoveImsFeatureInternal(slotId, feature, c);
- }
- }
-
- @Override
- public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent,
- IImsRegistrationListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.startSession(incomingCallIntent, listener);
- }
- }
- return 0;
- }
-
- @Override
- public void endSession(int slotId, int featureType, int sessionId) throws RemoteException {
- synchronized (mFeatures) {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession");
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.endSession(sessionId);
- }
- }
- }
-
- @Override
- public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
- throws RemoteException {
- enforceReadPhoneStatePermission("isConnected");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.isConnected(callSessionType, callType);
- }
- }
- return false;
- }
-
- @Override
- public boolean isOpened(int slotId, int featureType) throws RemoteException {
- enforceReadPhoneStatePermission("isOpened");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.isOpened();
- }
- }
- return false;
- }
-
- @Override
- public int getFeatureStatus(int slotId, int featureType) throws RemoteException {
- enforceReadPhoneStatePermission("getFeatureStatus");
- int status = ImsFeature.STATE_NOT_AVAILABLE;
- synchronized (mFeatures) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap != null) {
- ImsFeature feature = getImsFeatureFromType(featureMap, featureType);
- if (feature != null) {
- status = feature.getFeatureState();
- }
- }
- }
- return status;
- }
-
- @Override
- public void addRegistrationListener(int slotId, int featureType,
- IImsRegistrationListener listener) throws RemoteException {
- enforceReadPhoneStatePermission("addRegistrationListener");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.addRegistrationListener(listener);
- }
- }
- }
-
- @Override
- public void removeRegistrationListener(int slotId, int featureType,
- IImsRegistrationListener listener) throws RemoteException {
- enforceReadPhoneStatePermission("removeRegistrationListener");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.removeRegistrationListener(listener);
- }
- }
- }
-
- @Override
- public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
- int callSessionType, int callType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.createCallProfile(sessionId, callSessionType, callType);
- }
- }
- return null;
- }
-
- @Override
- public IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
- ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.createCallSession(sessionId, profile, listener);
- }
- }
- return null;
- }
-
- @Override
- public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
- String callId) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getPendingCallSession(sessionId, callId);
- }
- }
- return null;
- }
-
- @Override
- public IImsUt getUtInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getUtInterface();
- }
- }
- return null;
- }
-
- @Override
- public IImsConfig getConfigInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getConfigInterface();
- }
- }
- return null;
- }
-
- @Override
- public void turnOnIms(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.turnOnIms();
- }
- }
- }
-
- @Override
- public void turnOffIms(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.turnOffIms();
- }
- }
- }
-
- @Override
- public IImsEcbm getEcbmInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getEcbmInterface();
- }
- }
- return null;
- }
-
- @Override
- public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- feature.setUiTTYMode(uiTtyMode, onComplete);
- }
- }
- }
-
- @Override
- public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
- throws RemoteException {
- enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface");
- synchronized (mFeatures) {
- MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
- if (feature != null) {
- return feature.getMultiEndpointInterface();
- }
- }
- return null;
- }
-
};
/**
@@ -341,127 +121,93 @@
@Override
public IBinder onBind(Intent intent) {
if(SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.i(LOG_TAG, "ImsService Bound.");
return mImsServiceController;
}
return null;
}
/**
- * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and
- * featureType
- * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
- * @param featureType An integer representing the type of ImsFeature being created. This is
- * defined in {@link ImsFeature}.
+ * @hide
*/
- // Be sure to lock on mFeatures before accessing this method
- private void onCreateImsFeatureInternal(int slotId, int featureType,
+ @VisibleForTesting
+ public SparseArray<ImsFeature> getFeatures(int slotId) {
+ return mFeaturesBySlot.get(slotId);
+ }
+
+ private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
IImsFeatureStatusCallback c) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap == null) {
- featureMap = new SparseArray<>();
- mFeatures.put(slotId, featureMap);
- }
- ImsFeature f = makeImsFeature(slotId, featureType);
+ MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
if (f != null) {
- f.setContext(this);
- f.setSlotId(slotId);
- f.addImsFeatureStatusCallback(c);
- featureMap.put(featureType, f);
- }
-
- }
- /**
- * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and
- * featureType.
- * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
- * @param featureType An integer representing the type of ImsFeature being removed. This is
- * defined in {@link ImsFeature}.
- */
- // Be sure to lock on mFeatures before accessing this method
- private void onRemoveImsFeatureInternal(int slotId, int featureType,
- IImsFeatureStatusCallback c) {
- SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
- if (featureMap == null) {
- return;
- }
-
- ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType);
- if (featureToRemove != null) {
- featureMap.remove(featureType);
- featureToRemove.notifyFeatureRemoved(slotId);
- // Remove reference to Binder
- featureToRemove.removeImsFeatureStatusCallback(c);
- }
- }
-
- // Be sure to lock on mFeatures before accessing this method
- private MMTelFeature resolveMMTelFeature(int slotId, int featureType) {
- SparseArray<ImsFeature> features = getImsFeatureMap(slotId);
- MMTelFeature feature = null;
- if (features != null) {
- feature = resolveImsFeature(features, featureType, MMTelFeature.class);
- }
- return feature;
- }
-
- // Be sure to lock on mFeatures before accessing this method
- private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType,
- Class<T> className) {
- ImsFeature feature = getImsFeatureFromType(set, featureType);
- if (feature == null) {
+ setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+ return f.getBinder();
+ } else {
return null;
}
- try {
- return className.cast(feature);
- } catch (ClassCastException e)
- {
- Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage());
+ }
+
+ private IImsMMTelFeature createMMTelFeatureInternal(int slotId,
+ IImsFeatureStatusCallback c) {
+ MMTelFeature f = onCreateMMTelImsFeature(slotId);
+ if (f != null) {
+ setupFeature(f, slotId, ImsFeature.MMTEL, c);
+ return f.getBinder();
+ } else {
+ return null;
}
- return null;
}
- /**
- * @hide
- */
- @VisibleForTesting
- // Be sure to lock on mFeatures before accessing this method
- public SparseArray<ImsFeature> getImsFeatureMap(int slotId) {
- return mFeatures.get(slotId);
- }
-
- /**
- * @hide
- */
- @VisibleForTesting
- // Be sure to lock on mFeatures before accessing this method
- public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) {
- return set.get(featureType);
- }
-
- private ImsFeature makeImsFeature(int slotId, int feature) {
- switch (feature) {
- case ImsFeature.EMERGENCY_MMTEL: {
- return onCreateEmergencyMMTelImsFeature(slotId);
- }
- case ImsFeature.MMTEL: {
- return onCreateMMTelImsFeature(slotId);
- }
- case ImsFeature.RCS: {
- return onCreateRcsFeature(slotId);
- }
+ private IImsRcsFeature createRcsFeatureInternal(int slotId,
+ IImsFeatureStatusCallback c) {
+ RcsFeature f = onCreateRcsFeature(slotId);
+ if (f != null) {
+ setupFeature(f, slotId, ImsFeature.RCS, c);
+ return f.getBinder();
+ } else {
+ return null;
}
- // Tried to create feature that is not defined.
- return null;
}
- /**
- * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a
- * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps.
- */
- private void enforceReadPhoneStatePermission(String fn) {
- if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, fn);
+ private void setupFeature(ImsFeature f, int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ f.setContext(this);
+ f.setSlotId(slotId);
+ f.addImsFeatureStatusCallback(c);
+ addImsFeature(slotId, featureType, f);
+ }
+
+ private void addImsFeature(int slotId, int featureType, ImsFeature f) {
+ synchronized (mFeaturesBySlot) {
+ // Get SparseArray for Features, by querying slot Id
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ // Populate new SparseArray of features if it doesn't exist for this slot yet.
+ features = new SparseArray<>();
+ mFeaturesBySlot.put(slotId, features);
+ }
+ features.put(featureType, f);
+ }
+ }
+
+ private void removeImsFeature(int slotId, int featureType,
+ IImsFeatureStatusCallback c) {
+ synchronized (mFeaturesBySlot) {
+ // get ImsFeature associated with the slot/feature
+ SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+ if (features == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot "
+ + slotId);
+ return;
+ }
+ ImsFeature f = features.get(featureType);
+ if (f == null) {
+ Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type "
+ + featureType + " exists on slot " + slotId);
+ return;
+ }
+ f.removeImsFeatureStatusCallback(c);
+ f.onFeatureRemoved();
+ features.remove(featureType);
}
}
@@ -470,7 +216,7 @@
* functionality. Must be able to handle emergency calls at any time as well.
* @hide
*/
- public MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
+ public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
return null;
}
@@ -479,7 +225,7 @@
* functionality.
* @hide
*/
- public MMTelFeature onCreateMMTelImsFeature(int slotId) {
+ public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) {
return null;
}
@@ -487,7 +233,7 @@
* @return An implementation of RcsFeature that will be used by the system for RCS.
* @hide
*/
- public RcsFeature onCreateRcsFeature(int slotId) {
+ public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
return null;
}
}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 9d880b7..062858d 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
+import android.os.IInterface;
import android.os.RemoteException;
import android.telephony.SubscriptionManager;
import android.util.Log;
@@ -91,17 +92,12 @@
public static final int STATE_INITIALIZING = 1;
public static final int STATE_READY = 2;
- private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
private @ImsState int mState = STATE_NOT_AVAILABLE;
private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private Context mContext;
- public interface INotifyFeatureRemoved {
- void onFeatureRemoved(int slotId);
- }
-
public void setContext(Context context) {
mContext = context;
}
@@ -110,26 +106,6 @@
mSlotId = slotId;
}
- public void addFeatureRemovedListener(INotifyFeatureRemoved listener) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.add(listener);
- }
- }
-
- public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.remove(listener);
- }
- }
-
- // Not final for testing.
- public void notifyFeatureRemoved(int slotId) {
- synchronized (mRemovedListeners) {
- mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId));
- onFeatureRemoved();
- }
- }
-
public int getFeatureState() {
return mState;
}
@@ -215,4 +191,9 @@
* Called when the feature is being removed and must be cleaned up.
*/
public abstract void onFeatureRemoved();
+
+ /**
+ * @return Binder instance
+ */
+ public abstract IInterface getBinder();
}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 758c379..e790d14 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -18,18 +18,18 @@
import android.app.PendingIntent;
import android.os.Message;
+import android.os.RemoteException;
import com.android.ims.ImsCallProfile;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
import com.android.ims.internal.IImsUt;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.ims.internal.ImsCallSession;
/**
* Base implementation for MMTel.
@@ -41,6 +41,146 @@
public class MMTelFeature extends ImsFeature {
+ // Lock for feature synchronization
+ private final Object mLock = new Object();
+
+ private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() {
+
+ @Override
+ public int startSession(PendingIntent incomingCallIntent,
+ IImsRegistrationListener listener) throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.startSession(incomingCallIntent, listener);
+ }
+ }
+
+ @Override
+ public void endSession(int sessionId) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.endSession(sessionId);
+ }
+ }
+
+ @Override
+ public boolean isConnected(int callSessionType, int callType)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.isConnected(callSessionType, callType);
+ }
+ }
+
+ @Override
+ public boolean isOpened() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.isOpened();
+ }
+ }
+
+ @Override
+ public int getFeatureStatus() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getFeatureState();
+ }
+ }
+
+ @Override
+ public void addRegistrationListener(IImsRegistrationListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.addRegistrationListener(listener);
+ }
+ }
+
+ @Override
+ public void removeRegistrationListener(IImsRegistrationListener listener)
+ throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.removeRegistrationListener(listener);
+ }
+ }
+
+ @Override
+ public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.createCallProfile(sessionId, callSessionType, callType);
+ }
+ }
+
+ @Override
+ public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+ IImsCallSessionListener listener) throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.createCallSession(sessionId, profile, listener);
+ }
+ }
+
+ @Override
+ public IImsCallSession getPendingCallSession(int sessionId, String callId)
+ throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getPendingCallSession(sessionId, callId);
+ }
+ }
+
+ @Override
+ public IImsUt getUtInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getUtInterface();
+ }
+ }
+
+ @Override
+ public IImsConfig getConfigInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getConfigInterface();
+ }
+ }
+
+ @Override
+ public void turnOnIms() throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.turnOnIms();
+ }
+ }
+
+ @Override
+ public void turnOffIms() throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.turnOffIms();
+ }
+ }
+
+ @Override
+ public IImsEcbm getEcbmInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getEcbmInterface();
+ }
+ }
+
+ @Override
+ public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException {
+ synchronized (mLock) {
+ MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete);
+ }
+ }
+
+ @Override
+ public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MMTelFeature.this.getMultiEndpointInterface();
+ }
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ public final IImsMMTelFeature getBinder() {
+ return mImsMMTelBinder;
+ }
+
/**
* Notifies the MMTel feature that you would like to start a session. This should always be
* done before making/receiving IMS calls. The IMS service will register the device to the
@@ -135,7 +275,7 @@
}
/**
- * Creates a {@link ImsCallSession} with the specified call profile.
+ * Creates an {@link ImsCallSession} with the specified call profile.
* Use other methods, if applicable, instead of interacting with
* {@link ImsCallSession} directly.
*
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 332cca3..a82e608 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -16,6 +16,8 @@
package android.telephony.ims.feature;
+import com.android.ims.internal.IImsRcsFeature;
+
/**
* Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
* this class and provide implementations of the RcsFeature methods that they support.
@@ -24,6 +26,11 @@
public class RcsFeature extends ImsFeature {
+ private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() {
+ // Empty Default Implementation.
+ };
+
+
public RcsFeature() {
super();
}
@@ -32,4 +39,9 @@
public void onFeatureRemoved() {
}
+
+ @Override
+ public final IImsRcsFeature getBinder() {
+ return mImsRcsBinder;
+ }
}
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
new file mode 100644
index 0000000..52b3853
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -0,0 +1,56 @@
+/*
+ * 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.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsUt;
+
+import android.os.Message;
+
+/**
+ * See MMTelFeature for more information.
+ * {@hide}
+ */
+interface IImsMMTelFeature {
+ int startSession(in PendingIntent incomingCallIntent,
+ in IImsRegistrationListener listener);
+ void endSession(int sessionId);
+ boolean isConnected(int callSessionType, int callType);
+ boolean isOpened();
+ int getFeatureStatus();
+ void addRegistrationListener(in IImsRegistrationListener listener);
+ void removeRegistrationListener(in IImsRegistrationListener listener);
+ ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType);
+ IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile,
+ IImsCallSessionListener listener);
+ IImsCallSession getPendingCallSession(int sessionId, String callId);
+ IImsUt getUtInterface();
+ IImsConfig getConfigInterface();
+ void turnOnIms();
+ void turnOffIms();
+ IImsEcbm getEcbmInterface();
+ void setUiTTYMode(int uiTtyMode, in Message onComplete);
+ IImsMultiEndpoint getMultiEndpointInterface();
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
similarity index 65%
copy from telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
copy to telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
index df10700..b1cb23b 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
@@ -17,12 +17,9 @@
package com.android.ims.internal;
/**
- * Interface from ImsResolver to ImsServiceProxy in ImsManager.
- * Callback to ImsManager when a feature changes in the ImsServiceController.
+ * See RcsFeature for more information.
* {@hide}
*/
-oneway interface IImsServiceFeatureListener {
- void imsFeatureCreated(int slotId, int feature);
- void imsFeatureRemoved(int slotId, int feature);
- void imsStatusChanged(int slotId, int feature, int status);
+interface IImsRcsFeature {
+ //Empty Default Implementation
}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index f1e2262..857089f 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -16,49 +16,17 @@
package com.android.ims.internal;
-import android.app.PendingIntent;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsUt;
-
-import android.os.Message;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
/**
* See ImsService and MMTelFeature for more information.
* {@hide}
*/
interface IImsServiceController {
- // ImsService Control
- void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
- void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
- // MMTel Feature
- int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
- in IImsRegistrationListener listener);
- void endSession(int slotId, int featureType, int sessionId);
- boolean isConnected(int slotId, int featureType, int callSessionType, int callType);
- boolean isOpened(int slotId, int featureType);
- int getFeatureStatus(int slotId, int featureType);
- void addRegistrationListener(int slotId, int featureType, in IImsRegistrationListener listener);
- void removeRegistrationListener(int slotId, int featureType,
- in IImsRegistrationListener listener);
- ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
- int callSessionType, int callType);
- IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
- in ImsCallProfile profile, IImsCallSessionListener listener);
- IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
- String callId);
- IImsUt getUtInterface(int slotId, int featureType);
- IImsConfig getConfigInterface(int slotId, int featureType);
- void turnOnIms(int slotId, int featureType);
- void turnOffIms(int slotId, int featureType);
- IImsEcbm getEcbmInterface(int slotId, int featureType);
- void setUiTTYMode(int slotId, int featureType, int uiTtyMode, in Message onComplete);
- IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType);
+ IImsMMTelFeature createEmergencyMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
+ IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
+ IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
+ void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
similarity index 95%
rename from telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
rename to telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
index df10700..9a9cf53 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
@@ -21,7 +21,7 @@
* Callback to ImsManager when a feature changes in the ImsServiceController.
* {@hide}
*/
-oneway interface IImsServiceFeatureListener {
+oneway interface IImsServiceFeatureCallback {
void imsFeatureCreated(int slotId, int feature);
void imsFeatureRemoved(int slotId, int feature);
void imsStatusChanged(int slotId, int feature, int status);
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3cc9bde..fd6091a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -38,8 +38,9 @@
import android.telephony.SignalStrength;
import android.telephony.TelephonyHistogram;
import android.telephony.VisualVoicemailSmsFilterSettings;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.OperatorInfo;
@@ -784,12 +785,27 @@
int getTetherApnRequired();
/**
- * Get ImsServiceController binder from ImsResolver that corresponds to the subId and feature
- * requested as well as registering the ImsServiceController for callbacks using the
- * IImsServiceFeatureListener interface.
+ * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature
+ * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback
+ * interface.
*/
- IImsServiceController getImsServiceControllerAndListen(int slotIndex, int feature,
- IImsServiceFeatureListener callback);
+ IImsMMTelFeature getMMTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
+
+ /**
+ * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature
+ * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback
+ * interface.
+ * Used for emergency calling only.
+ */
+ IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId,
+ in IImsServiceFeatureCallback callback);
+
+ /**
+ * Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature
+ * as well as registering the RcsFeature for callbacks using the IImsServiceFeatureCallback
+ * interface.
+ */
+ IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
/**
* Set the network selection mode to automatic.
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 132b234..ffcef89 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -346,22 +346,20 @@
can_be_conditional &= CollectLocations(location, keep_set, &locations);
}
- for (const UsageLocation& location : entry.second) {
- printer.Print("# Referenced at ").Println(location.source.to_string());
- }
if (keep_set.conditional_keep_rules_ && can_be_conditional) {
- printer.Println("-if class **.R$layout {");
- printer.Indent();
for (const UsageLocation& location : locations) {
- printer.Print("int ")
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ printer.Print("-if class **.R$layout { int ")
.Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
- .Println(";");
+ .Println("; }");
+ printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
}
- printer.Undent();
- printer.Println("}");
- printer.Println();
+ } else {
+ for (const UsageLocation& location : entry.second) {
+ printer.Print("# Referenced at ").Println(location.source.to_string());
+ }
+ printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
}
- printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
printer.Println();
}