Merge "Fix permission checking for a11y volume."
diff --git a/Android.mk b/Android.mk
index 749242d..8daab8f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -140,8 +140,8 @@
core/java/android/bluetooth/IBluetoothInputHost.aidl \
core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
core/java/android/bluetooth/IBluetoothGatt.aidl \
- core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl \
- core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl \
+ core/java/android/bluetooth/IBluetoothGattCallback.aidl \
+ core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
core/java/android/bluetooth/le/IAdvertiserCallback.aidl \
core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl \
core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl \
@@ -567,8 +567,9 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
framework-protos \
- android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
+ android.hardware.thermal@1.0-java-constants \
+ android.hardware.tv.input@1.0-java-constants \
android.hardware.usb@1.0-java-constants \
android.hardware.vibrator@1.0-java-constants \
diff --git a/api/current.txt b/api/current.txt
index 8f969c1..8d27905 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21818,23 +21818,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21847,7 +21847,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -22275,7 +22290,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23586,10 +23601,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
@@ -24326,9 +24337,9 @@
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
method public void setShuffleModeEnabled(boolean);
- field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
- field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
public static abstract class MediaSession.Callback {
@@ -34858,7 +34869,7 @@
field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
- field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+ field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -37842,6 +37853,7 @@
method public void onRangeStart(java.lang.String, int, int, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 48b878e..badd31e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@
field public static final java.lang.String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
+ field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
field public static final java.lang.String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final java.lang.String CONTROL_VPN = "android.permission.CONTROL_VPN";
@@ -23604,23 +23605,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -23633,7 +23634,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -24061,7 +24077,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -25383,10 +25399,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
@@ -26195,9 +26207,9 @@
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
method public void setShuffleModeEnabled(boolean);
- field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
- field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
public static abstract class MediaSession.Callback {
@@ -37748,7 +37760,6 @@
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
- field public static final java.lang.String ACTION_CONFIGURE_WIFI_SETTINGS = "android.settings.CONFIGURE_WIFI_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -37798,7 +37809,6 @@
field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
field public static final java.lang.String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
- field public static final java.lang.String ACTION_WIFI_SAVED_NETWORK_SETTINGS = "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
field public static final java.lang.String ACTION_ZEN_MODE_PRIORITY_SETTINGS = "android.settings.ZEN_MODE_PRIORITY_SETTINGS";
@@ -37871,7 +37881,7 @@
field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
- field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+ field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -40976,6 +40986,7 @@
method public void onRangeStart(java.lang.String, int, int, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4d8d7f2..3a23cd9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4031,6 +4031,8 @@
method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
method public void setLaunchStackId(int);
+ method public void setLaunchTaskId(int);
+ method public void setTaskOverlay(boolean, boolean);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -11750,7 +11752,7 @@
}
public final class PageViewCursor extends android.database.CursorWrapper implements android.database.CrossProcessCursor {
- ctor public PageViewCursor(android.database.Cursor, int, int);
+ ctor public PageViewCursor(android.database.Cursor, android.os.Bundle);
method public void fillWindow(int, android.database.CursorWindow);
method public android.database.CursorWindow getWindow();
method public boolean onMove(int, int);
@@ -21929,23 +21931,23 @@
}
public final class MediaCas {
- ctor public MediaCas(int) throws android.media.UnsupportedCasException;
+ ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
method public void closeSession(byte[]);
method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
method public static boolean isSystemIdSupported(int);
- method public byte[] openSession(int);
- method public byte[] openSession(int, int);
- method public void processEcm(byte[], byte[], int, int);
- method public void processEcm(byte[], byte[]);
- method public void processEmm(byte[], int, int);
- method public void processEmm(byte[]);
- method public void provision(java.lang.String);
- method public void refreshEntitlements(int, byte[]);
+ method public byte[] openSession(int) throws android.media.MediaCasException;
+ method public byte[] openSession(int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[], int, int) throws android.media.MediaCasException;
+ method public void processEcm(byte[], byte[]) throws android.media.MediaCasException;
+ method public void processEmm(byte[], int, int) throws android.media.MediaCasException;
+ method public void processEmm(byte[]) throws android.media.MediaCasException;
+ method public void provision(java.lang.String) throws android.media.MediaCasException;
+ method public void refreshEntitlements(int, byte[]) throws android.media.MediaCasException;
method public void release();
- method public void sendEvent(int, int, byte[]);
+ method public void sendEvent(int, int, byte[]) throws android.media.MediaCasException;
method public void setEventListener(android.media.MediaCas.EventListener, android.os.Handler);
- method public void setPrivateData(byte[]);
- method public void setSessionPrivateData(byte[], byte[]);
+ method public void setPrivateData(byte[]) throws android.media.MediaCasException;
+ method public void setSessionPrivateData(byte[], byte[]) throws android.media.MediaCasException;
}
public static abstract interface MediaCas.EventListener {
@@ -21958,7 +21960,22 @@
}
public class MediaCasException extends java.lang.Exception {
- ctor public MediaCasException(java.lang.String);
+ }
+
+ public static final class MediaCasException.DeniedByServerException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.NotProvisionedException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.ResourceBusyException extends android.media.MediaCasException {
+ }
+
+ public static final class MediaCasException.UnsupportedCasException extends android.media.MediaCasException {
+ }
+
+ public class MediaCasStateException extends java.lang.IllegalStateException {
+ method public java.lang.String getDiagnosticInfo();
}
public final class MediaCodec {
@@ -22386,7 +22403,7 @@
}
public final class MediaDescrambler {
- ctor public MediaDescrambler(int) throws android.media.UnsupportedCasException;
+ ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException;
method public final int descramble(java.nio.ByteBuffer, int, java.nio.ByteBuffer, int, android.media.MediaCodec.CryptoInfo);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
@@ -23697,10 +23714,6 @@
field public static final int TONE_SUP_RINGTONE = 23; // 0x17
}
- public final class UnsupportedCasException extends android.media.MediaCasException {
- ctor public UnsupportedCasException(java.lang.String);
- }
-
public final class UnsupportedSchemeException extends android.media.MediaDrmException {
ctor public UnsupportedSchemeException(java.lang.String);
}
@@ -24437,9 +24450,9 @@
method public void setRepeatMode(int);
method public void setSessionActivity(android.app.PendingIntent);
method public void setShuffleModeEnabled(boolean);
- field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final deprecated int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
- field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final deprecated int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
}
public static abstract class MediaSession.Callback {
@@ -34997,7 +35010,7 @@
field public static final java.lang.String WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN = "wifi_device_owner_configs_lockdown";
field public static final java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
field public static final java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms";
- field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
+ field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on";
field public static final java.lang.String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY = "wifi_networks_available_repeat_delay";
field public static final java.lang.String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
field public static final java.lang.String WIFI_ON = "wifi_on";
@@ -38043,6 +38056,7 @@
method public void onRangeStart(java.lang.String, int, int, int);
method public abstract void onStart(java.lang.String);
method public void onStop(java.lang.String, boolean);
+ method public deprecated void onUtteranceRangeStart(java.lang.String, int, int);
}
public class Voice implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0b9479d..aa7cdf7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1068,6 +1068,7 @@
* Sets the task the activity will be launched in.
* @hide
*/
+ @TestApi
public void setLaunchTaskId(int taskId) {
mLaunchTaskId = taskId;
}
@@ -1085,6 +1086,7 @@
* the task will also not be moved to the front of the stack.
* @hide
*/
+ @TestApi
public void setTaskOverlay(boolean taskOverlay, boolean canResume) {
mTaskOverlay = taskOverlay;
mTaskOverlayCanResume = canResume;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 9e2eb84..aa61ce2 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -135,8 +135,8 @@
/**
* Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
*/
- private final IBluetoothGattCallbackExt mBluetoothGattCallback =
- new IBluetoothGattCallbackExt.Stub() {
+ private final IBluetoothGattCallback mBluetoothGattCallback =
+ new IBluetoothGattCallback.Stub() {
/**
* Application interface registered - app is ready to go
* @hide
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index c991e2f..b35a593 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -59,8 +59,8 @@
/**
* Bluetooth GATT interface callbacks
*/
- private final IBluetoothGattServerCallbackExt mBluetoothGattServerCallback =
- new IBluetoothGattServerCallbackExt.Stub() {
+ private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+ new IBluetoothGattServerCallback.Stub() {
/**
* Application interface registered - app is ready to go
* @hide
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 652a1c6..0825ee8 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -29,8 +29,8 @@
import android.os.ParcelUuid;
import android.os.WorkSource;
-import android.bluetooth.IBluetoothGattCallbackExt;
-import android.bluetooth.IBluetoothGattServerCallbackExt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
import android.bluetooth.le.IAdvertiserCallback;
import android.bluetooth.le.IAdvertisingSetCallback;
import android.bluetooth.le.IPeriodicAdvertisingCallback;
@@ -66,7 +66,7 @@
void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
void unregisterSync(in IPeriodicAdvertisingCallback callback);
- void registerClient(in ParcelUuid appId, in IBluetoothGattCallbackExt callback);
+ void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
void unregisterClient(in int clientIf);
void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport, in int phy);
@@ -88,7 +88,7 @@
void configureMTU(in int clientIf, in String address, in int mtu);
void connectionParameterUpdate(in int clientIf, in String address, in int connectionPriority);
- void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallbackExt callback);
+ void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
void unregisterServer(in int serverIf);
void serverConnect(in int serverIf, in String address, in boolean isDirect, in int transport);
void serverDisconnect(in int serverIf, in String address);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
similarity index 97%
rename from core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattCallback.aidl
index ed69e54..4f85cdd 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -22,7 +22,7 @@
* Callback definitions for interacting with BLE / GATT
* @hide
*/
-oneway interface IBluetoothGattCallbackExt {
+oneway interface IBluetoothGattCallback {
void onClientRegistered(in int status, in int clientIf);
void onClientConnectionState(in int status, in int clientIf,
in boolean connected, in String address);
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
similarity index 97%
rename from core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
rename to core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index 267e882..74ee11f 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -21,7 +21,7 @@
* Callback definitions for interacting with BLE / GATT
* @hide
*/
-oneway interface IBluetoothGattServerCallbackExt {
+oneway interface IBluetoothGattServerCallback {
void onServerRegistered(in int status, in int serverIf);
void onServerConnectionState(in int status, in int serverIf,
in boolean connected, in String address);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1fafe65..d264e09 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4400,8 +4400,12 @@
defaultMaxAspectRatio = owner.applicationInfo.maxAspectRatio;
}
- aInfo.maxAspectRatio = Math.max(1.0f, sa.getFloat(
- R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio));
+ aInfo.maxAspectRatio = sa.getFloat(
+ R.styleable.AndroidManifestActivity_maxAspectRatio, defaultMaxAspectRatio);
+ if (aInfo.maxAspectRatio < 1.0f && aInfo.maxAspectRatio != 0) {
+ // Ignore any value lesser than 1.0.
+ aInfo.maxAspectRatio = 0;
+ }
}
/**
diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java
index 44727a0..4569a27 100644
--- a/core/java/android/database/PageViewCursor.java
+++ b/core/java/android/database/PageViewCursor.java
@@ -15,6 +15,7 @@
*/
package android.database;
+import static com.android.internal.util.ArrayUtils.contains;
import static com.android.internal.util.Preconditions.checkArgument;
import android.annotation.Nullable;
@@ -25,7 +26,7 @@
import android.util.Log;
import android.util.MathUtils;
-import com.android.internal.util.ArrayUtils;
+import java.util.Arrays;
/**
* Cursor wrapper that provides visibility into a subset of a wrapped cursor.
@@ -40,11 +41,12 @@
/** An extra added to results that are auto-paged using the wrapper. */
public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED";
+ private static final String[] EMPTY_ARGS = new String[0];
private static final String TAG = "PageViewCursor";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final boolean VERBOSE = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE);
- private final int mOffset; // aka first index
+ private final int mOffset; // aka first index
private final int mCount;
private final Bundle mExtras;
@@ -55,12 +57,17 @@
/**
* @see PageViewCursor#wrap(Cursor, Bundle)
*/
- public PageViewCursor(Cursor cursor, int offset, int limit) {
+ public PageViewCursor(Cursor cursor, Bundle queryArgs) {
super(cursor);
+ int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
+ int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE);
+
checkArgument(offset > -1);
checkArgument(limit > -1);
+ int count = mCursor.getCount();
+
mOffset = offset;
mExtras = new Bundle();
@@ -68,26 +75,47 @@
if (extras != null) {
mExtras.putAll(extras);
}
- mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
-
- // We need a mutable bundle so we can add QUERY_RESULT_SIZE.
- // Direct equality check is correct here. Bundle.EMPTY is a specific instance
- // of Bundle that is immutable by way of implementation.
- // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras;
// When we're wrapping another cursor, it should not already be "paged".
- checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE));
+ checkArgument(!hasPagedResponseDetails(mExtras));
- int count = mCursor.getCount();
+ mExtras.putBoolean(EXTRA_AUTO_PAGED, true);
mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count);
+ // Ensure we retain any extra args supplied in cursor extras, and add
+ // offset and/or limit.
+ String[] existingArgs = mExtras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+ existingArgs = existingArgs != null ? existingArgs : EMPTY_ARGS;
+
+ int size = existingArgs.length;
+
+ // copy the array with space for the extra query args we'll be adding.
+ String[] newArgs = Arrays.copyOf(existingArgs, size + 2);
+
+ if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
+ newArgs[size++] = ContentResolver.QUERY_ARG_OFFSET;
+ }
+ if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
+ newArgs[size++] = ContentResolver.QUERY_ARG_LIMIT;
+ }
+
+ assert(size > existingArgs.length); // must add at least one arg.
+
+ // At this point there may be a null element at the end of
+ // the array because our pre-sizing didn't match the actualy
+ // number of args we added. So we trim.
+ if (size == newArgs.length - 1) {
+ newArgs = Arrays.copyOf(newArgs, size);
+ }
+ mExtras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, newArgs);
+
mCount = MathUtils.constrain(count - offset, 0, limit);
if (DEBUG) Log.d(TAG, "Wrapped cursor"
- + " offset: " + mOffset
- + ", limit: " + limit
- + ", delegate_size: " + count
- + ", paged_count: " + mCount);
+ + " offset: " + mOffset
+ + ", limit: " + limit
+ + ", delegate_size: " + count
+ + ", paged_count: " + mCount);
}
@Override
@@ -155,9 +183,9 @@
public boolean moveToPosition(int position) {
if (position >= mCount) {
if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount
- + ". Moving to last record.");
+ + ". Moving to last record.");
mPos = mCount;
- super.moveToPosition(mOffset + mPos); // move into "after last" state.
+ super.moveToPosition(mOffset + mPos); // move into "after last" state.
return false;
}
@@ -198,15 +226,15 @@
@Override
public boolean getWantsAllOnMoveCalls() {
- return false; // we want bulk cursor adapter to lift data into a CursorWindow.
+ return false; // we want bulk cursor adapter to lift data into a CursorWindow.
}
@Override
public CursorWindow getWindow() {
assert(mPos == -1 || mPos == 0);
if (mWindow == null) {
- mWindow = new CursorWindow("PageViewCursorWindow");
- fillWindow(0, mWindow);
+ mWindow = new CursorWindow("PageViewCursorWindow");
+ fillWindow(0, mWindow);
}
return mWindow;
@@ -224,17 +252,16 @@
}
/**
- * Wraps the cursor such that it will honor paging args (if present), AND if the cursor
- * does not report paging size.
- *
- * <p>No-op if cursor already contains paging or is less than specified page size.
+ * Wraps the cursor such that it will honor paging args (if present), AND if the cursor does
+ * not report paging size.
+ * <p>
+ * No-op if cursor already contains paging or is less than specified page size.
*/
public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) {
- boolean hasPagingArgs =
- queryArgs != null
+ boolean hasPagingArgs = queryArgs != null
&& (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)
- || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
+ || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT));
if (!hasPagingArgs) {
if (VERBOSE) Log.v(TAG, "No-wrap: No paging args in request.");
@@ -253,25 +280,26 @@
return cursor;
}
- return new PageViewCursor(
- cursor,
- queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0),
- queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE));
+ return new PageViewCursor(cursor, queryArgs);
}
/**
- * @return true if the extras contains information indicating the associated
- * cursor is paged.
+ * @return true if the extras contains information indicating the associated cursor is
+ * paged.
*/
private static boolean hasPagedResponseDetails(@Nullable Bundle extras) {
- if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) {
+ if (extras == null) {
+ return false;
+ }
+
+ if (extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) {
return true;
}
String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
- if (honoredArgs != null && (
- ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
- || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
+ if (honoredArgs != null
+ && (contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET)
+ || contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) {
return true;
}
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index dcd069d..da771e4 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -118,6 +118,11 @@
* the Surface provided to prepare must not be used as a target of a CaptureRequest submitted
* to this session.</p>
*
+ * <p>Note that if 2 surfaces share the same stream via {@link
+ * OutputConfiguration#enableSurfaceSharing} and {@link OutputConfiguration#addSurface},
+ * prepare() only needs to be called on one surface, and {link
+ * StateCallback#onSurfacePrepared} will be triggered for both surfaces.</p>
+ *
* <p>{@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}
* devices cannot pre-allocate output buffers; for those devices,
* {@link StateCallback#onSurfacePrepared} will be immediately called, and no preallocation is
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e3b97e8..e75b375 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -708,7 +708,8 @@
synchronized(mInterfaceLock) {
int streamId = -1;
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
- if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+ final List<Surface> surfaces = mConfiguredOutputs.valueAt(i).getSurfaces();
+ if (surfaces.contains(surface)) {
streamId = mConfiguredOutputs.keyAt(i);
break;
}
@@ -2020,9 +2021,10 @@
Log.w(TAG, "onPrepared invoked for unknown output Surface");
return;
}
- final Surface surface = output.getSurface();
-
- sessionCallback.onSurfacePrepared(surface);
+ final List<Surface> surfaces = output.getSurfaces();
+ for (Surface surface : surfaces) {
+ sessionCallback.onSurfacePrepared(surface);
+ }
}
@Override
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 70ecf89..29483cd 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -120,6 +120,14 @@
"android.net.wifi.use_open_wifi_package";
/**
+ * Meta-data specified on a {@link NetworkRecommendationProvider} that specifies the
+ * {@link android.app.NotificationChannel} ID used to post open network notifications.
+ * @hide
+ */
+ public static final String NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA =
+ "android.net.wifi.notification_channel_id_network_available";
+
+ /**
* Broadcast action: the active scorer has been changed. Scorer apps may listen to this to
* perform initialization once selected as the active scorer, or clean up unneeded resources
* if another scorer has been selected. This is an explicit broadcast only sent to the
diff --git a/core/java/android/net/NetworkScorerAppData.java b/core/java/android/net/NetworkScorerAppData.java
index 5bf1e10..1734b34 100644
--- a/core/java/android/net/NetworkScorerAppData.java
+++ b/core/java/android/net/NetworkScorerAppData.java
@@ -23,13 +23,20 @@
* wifi networks automatically" feature.
*/
private final ComponentName mEnableUseOpenWifiActivity;
+ /**
+ * The {@link android.app.NotificationChannel} ID used by {@link #mRecommendationService} to
+ * post open network notifications.
+ */
+ private final String mNetworkAvailableNotificationChannelId;
public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp,
- String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity) {
+ String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity,
+ String networkAvailableNotificationChannelId) {
this.packageUid = packageUid;
this.mRecommendationService = recommendationServiceComp;
this.mRecommendationServiceLabel = recommendationServiceLabel;
this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity;
+ this.mNetworkAvailableNotificationChannelId = networkAvailableNotificationChannelId;
}
protected NetworkScorerAppData(Parcel in) {
@@ -37,6 +44,7 @@
mRecommendationService = ComponentName.readFromParcel(in);
mRecommendationServiceLabel = in.readString();
mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in);
+ mNetworkAvailableNotificationChannelId = in.readString();
}
@Override
@@ -45,6 +53,7 @@
ComponentName.writeToParcel(mRecommendationService, dest);
dest.writeString(mRecommendationServiceLabel);
ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest);
+ dest.writeString(mNetworkAvailableNotificationChannelId);
}
@Override
@@ -83,6 +92,11 @@
return mRecommendationServiceLabel;
}
+ @Nullable
+ public String getNetworkAvailableNotificationChannelId() {
+ return mNetworkAvailableNotificationChannelId;
+ }
+
@Override
public String toString() {
return "NetworkScorerAppData{" +
@@ -90,6 +104,8 @@
", mRecommendationService=" + mRecommendationService +
", mRecommendationServiceLabel=" + mRecommendationServiceLabel +
", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity +
+ ", mNetworkAvailableNotificationChannelId=" +
+ mNetworkAvailableNotificationChannelId +
'}';
}
@@ -101,12 +117,14 @@
return packageUid == that.packageUid &&
Objects.equals(mRecommendationService, that.mRecommendationService) &&
Objects.equals(mRecommendationServiceLabel, that.mRecommendationServiceLabel) &&
- Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity);
+ Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity) &&
+ Objects.equals(mNetworkAvailableNotificationChannelId,
+ that.mNetworkAvailableNotificationChannelId);
}
@Override
public int hashCode() {
return Objects.hash(packageUid, mRecommendationService, mRecommendationServiceLabel,
- mEnableUseOpenWifiActivity);
+ mEnableUseOpenWifiActivity, mNetworkAvailableNotificationChannelId);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7005d44..5820211 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -396,38 +396,6 @@
"android.settings.WIFI_IP_SETTINGS";
/**
- * Activity Action: Show settings to allow the configuration of Wi-Fi features.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you
- * safeguard against this.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: Nothing.
- * @hide
- */
- @SystemApi
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_CONFIGURE_WIFI_SETTINGS =
- "android.settings.CONFIGURE_WIFI_SETTINGS";
-
- /**
- * Activity Action: Show settings to allow configuration of Wi-Fi saved networks.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you
- * safeguard against this.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: Nothing.
- * @hide
- */
- @SystemApi
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_WIFI_SAVED_NETWORK_SETTINGS =
- "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
-
- /**
* Activity Action: Show settings to allow configuration of Bluetooth.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -8232,9 +8200,14 @@
* the open network(s) disappear, we remove the notification. When we
* show the notification, we will not show it again for
* {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+ *
+ * @deprecated This feature is no longer controlled by this setting in
+ * {@link android.os.Build.VERSION_CODES#O}.
*/
+ @Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
"wifi_networks_available_notification_on";
+
/**
* {@hide}
*/
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index e59ec08..59ee8f3 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -137,7 +137,15 @@
* @param end The end index of the range (exclusive) in the utterance text.
* @param frame The position in frames in the audio of the request where this range is spoken.
*/
- public void onRangeStart(String utteranceId, int start, int end, int frame) {}
+ public void onRangeStart(String utteranceId, int start, int end, int frame) {
+ onUtteranceRangeStart(utteranceId, start, end);
+ }
+
+ /**
+ * @deprecated Due to internal API changes. Remove when apps catch up.
+ */
+ public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+ }
/**
* Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 9397a41..c68c449 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -26,6 +26,11 @@
System.loadLibrary("textclassifier");
}
+ /** Hints the classifier that this may be a url. */
+ static final int HINT_FLAG_URL = 0x01;
+ /** Hints the classifier that this may be an email. */
+ static final int HINT_FLAG_EMAIL = 0x02;
+
private final long mCtx;
/**
@@ -59,7 +64,7 @@
* scores for different collections.
*/
public ClassificationResult[] classifyText(
- String context, int selectionBegin, int selectionEnd) {
+ String context, int selectionBegin, int selectionEnd, int hintFlags) {
return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index be12f57..66a62c3 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,6 +35,7 @@
import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Log;
+import android.util.Patterns;
import android.view.View;
import com.android.internal.util.Preconditions;
@@ -88,7 +89,9 @@
if (start >= 0 && end <= string.length() && start <= end) {
final TextSelection.Builder tsBuilder = new TextSelection.Builder(start, end);
final SmartSelection.ClassificationResult[] results =
- getSmartSelection().classifyText(string, start, end);
+ getSmartSelection().classifyText(
+ string, start, end,
+ getHintFlags(string, start, end));
final int size = results.length;
for (int i = 0; i < size; i++) {
tsBuilder.setEntityType(results[i].mCollection, results[i].mScore);
@@ -116,12 +119,14 @@
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
- final CharSequence classified = text.subSequence(startIndex, endIndex);
+ final String string = text.toString();
SmartSelection.ClassificationResult[] results = getSmartSelection()
- .classifyText(text.toString(), startIndex, endIndex);
+ .classifyText(string, startIndex, endIndex,
+ getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
final TextClassificationResult classificationResult =
- createClassificationResult(results, classified);
+ createClassificationResult(
+ results, string.subSequence(startIndex, endIndex));
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format(
"Classification type: %s", classificationResult));
@@ -208,6 +213,24 @@
return builder.build();
}
+ private static int getHintFlags(CharSequence text, int start, int end) {
+ int flag = 0;
+ final CharSequence subText = text.subSequence(start, end);
+ if (Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(subText).matches()) {
+ flag |= SmartSelection.HINT_FLAG_EMAIL;
+ }
+ if (Patterns.AUTOLINK_WEB_URL.matcher(subText).matches()
+ && Linkify.sUrlMatchFilter.acceptMatch(text, start, end)) {
+ flag |= SmartSelection.HINT_FLAG_URL;
+ }
+ // TODO: Added this log for debug only. Remove before release.
+ Log.d(LOG_TAG, String.format("Email hint: %b",
+ (flag & SmartSelection.HINT_FLAG_EMAIL) != 0));
+ Log.d(LOG_TAG, String.format("Url hint: %b",
+ (flag & SmartSelection.HINT_FLAG_URL) != 0));
+ return flag;
+ }
+
private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
if (types.length < 1) {
return "";
@@ -262,7 +285,9 @@
if (selectionStart >= 0 && selectionEnd <= text.length()
&& selectionStart <= selectionEnd) {
final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(text, selectionStart, selectionEnd);
+ smartSelection.classifyText(
+ text, selectionStart, selectionEnd,
+ getHintFlags(text, selectionStart, selectionEnd));
if (results.length > 0) {
final String type = getHighestScoringType(results);
if (matches(type, linkMask)) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7d243af..59fb02d 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2064,7 +2064,7 @@
}
if (update) {
- update(mAnchor.get(), p);
+ update(mAnchor != null ? mAnchor.get() : null, p);
}
}
@@ -2371,7 +2371,8 @@
}
private class PopupDecorView extends FrameLayout {
- private TransitionListenerAdapter mPendingExitListener;
+ /** Runnable used to clean up listeners after exit transition. */
+ private Runnable mCleanupAfterExit;
public PopupDecorView(Context context) {
super(context);
@@ -2482,7 +2483,7 @@
* <p>
* <strong>Note:</strong> The transition listener is guaranteed to have
* its {@code onTransitionEnd} method called even if the transition
- * never starts; however, it may be called with a {@code null} argument.
+ * never starts.
*/
public void startExitTransition(@NonNull Transition transition,
@Nullable final View anchorRoot, @Nullable final Rect epicenter,
@@ -2498,25 +2499,32 @@
anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
}
- // The exit listener MUST be called for cleanup, even if the
- // transition never starts or ends. Stash it for later.
- mPendingExitListener = new TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition t) {
- if (anchorRoot != null) {
- anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
- }
+ // The cleanup runnable MUST be called even if the transition is
+ // canceled before it starts (and thus can't call onTransitionEnd).
+ mCleanupAfterExit = () -> {
+ listener.onTransitionEnd(transition);
- listener.onTransitionEnd(t);
-
- // The listener was called. Our job here is done.
- mPendingExitListener = null;
- t.removeListener(this);
+ if (anchorRoot != null) {
+ anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
}
+
+ // The listener was called. Our job here is done.
+ mCleanupAfterExit = null;
};
final Transition exitTransition = transition.clone();
- exitTransition.addListener(mPendingExitListener);
+ exitTransition.addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition t) {
+ t.removeListener(this);
+
+ // This null check shouldn't be necessary, but it's easier
+ // to check here than it is to test every possible case.
+ if (mCleanupAfterExit != null) {
+ mCleanupAfterExit.run();
+ }
+ }
+ });
exitTransition.setEpicenterCallback(new EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
@@ -2544,8 +2552,10 @@
public void cancelTransitions() {
TransitionManager.endTransitions(this);
- if (mPendingExitListener != null) {
- mPendingExitListener.onTransitionEnd(null);
+ // If the cleanup runnable is still around, that means the
+ // transition never started. We should run it now to clean up.
+ if (mCleanupAfterExit != null) {
+ mCleanupAfterExit.run();
}
}
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a032383..003db06 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -289,7 +289,7 @@
*/
private static final class TextClassificationHelper {
- private static final int TRIM_DELTA = 50; // characters
+ private static final int TRIM_DELTA = 120; // characters
private TextClassifier mTextClassifier;
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index 6d814bf..d2a9072 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -26,6 +26,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ReceiverCallNotAllowedException;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
@@ -86,24 +87,7 @@
private OnDismissedListener mDismissedListener;
private OnSwipeProgressChangedListener mProgressListener;
- private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
- private Runnable mRunnable = new Runnable() {
- @Override
- public void run() {
- if (mDismissed) {
- dismiss();
- } else {
- cancel();
- }
- resetMembers();
- }
- };
-
- @Override
- public void onReceive(Context context, Intent intent) {
- post(mRunnable);
- }
- };
+ private BroadcastReceiver mScreenOffReceiver;
private IntentFilter mScreenOffFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
@@ -146,12 +130,36 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- getContext().registerReceiver(mScreenOffReceiver, mScreenOffFilter);
+ try {
+ mScreenOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ post(() -> {
+ if (mDismissed) {
+ dismiss();
+ } else {
+ cancel();
+ }
+ resetMembers();
+ });
+ }
+ };
+ getContext().registerReceiver(mScreenOffReceiver, mScreenOffFilter);
+ } catch (ReceiverCallNotAllowedException e) {
+ /* Exception is thrown if the context is a ReceiverRestrictedContext object. As
+ * ReceiverRestrictedContext is not public, the context type cannot be checked before
+ * calling registerReceiver. The most likely scenario in which the exception would be
+ * thrown would be when a BroadcastReceiver creates a dialog to show the user. */
+ mScreenOffReceiver = null; // clear receiver since it was not used.
+ }
}
@Override
protected void onDetachedFromWindow() {
- getContext().unregisterReceiver(mScreenOffReceiver);
+ if (mScreenOffReceiver != null) {
+ getContext().unregisterReceiver(mScreenOffReceiver);
+ mScreenOffReceiver = null;
+ }
super.onDetachedFromWindow();
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1cd0afe..ce8a224 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -313,6 +313,10 @@
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
<protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -1346,7 +1350,7 @@
<permission android:name="android.permission.CONNECTIVITY_INTERNAL"
android:protectionLevel="signature|privileged" />
- <!-- Allows an internal user to use restricted Networks.
+ <!-- @SystemApi Allows an internal user to use restricted Networks.
@hide -->
<permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 68e766e..7704519 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2580,12 +2580,14 @@
<integer name="config_defaultPictureInPictureGravity">0x55</integer>
<!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any
- ratio smaller than this is considered too tall and thin to be usable. -->
- <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.5</item>
+ ratio smaller than this is considered too tall and thin to be usable. Currently, this
+ is the inverse of the max landscape aspect ratio (1:2.39), but this is an extremely
+ skinny aspect ratio that is not expected to be widely used. -->
+ <item name="config_pictureInPictureMinAspectRatio" format="float" type="dimen">0.41841004184</item>
<!-- The minimum aspect ratio (width/height) that is supported for picture-in-picture. Any
- ratio larger than this is considered to wide and short to be usable. -->
- <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.35</item>
+ ratio larger than this is considered to wide and short to be usable. Currently 2.39:1. -->
+ <item name="config_pictureInPictureMaxAspectRatio" format="float" type="dimen">2.39</item>
<!-- The snap mode to use for picture-in-picture. These values correspond to constants defined
in PipSnapAlgorithm and should not be changed independently.
diff --git a/core/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java
index 62b5410..fba6aaf 100644
--- a/core/tests/coretests/src/android/database/PageViewCursorTest.java
+++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java
@@ -13,20 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.database;
+import static com.android.internal.util.ArrayUtils.contains;
+import static com.android.internal.util.Preconditions.checkArgument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
import android.content.ContentResolver;
+import android.os.Build;
import android.os.Bundle;
import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.MathUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.Random;
@RunWith(AndroidJUnit4.class)
@@ -37,32 +45,32 @@
private static final String NAME_COLUMN = "name";
private static final String NUM_COLUMN = "num";
- private static final String[] COLUMNS = new String[]{
- NAME_COLUMN,
- NUM_COLUMN
+ private static final String[] COLUMNS = new String[] {
+ NAME_COLUMN,
+ NUM_COLUMN
};
private static final String[] NAMES = new String[] {
- "000",
- "111",
- "222",
- "333",
- "444",
- "555",
- "666",
- "777",
- "888",
- "999",
- "aaa",
- "bbb",
- "ccc",
- "ddd",
- "eee",
- "fff",
- "ggg",
- "hhh",
- "iii",
- "jjj"
+ "000",
+ "111",
+ "222",
+ "333",
+ "444",
+ "555",
+ "666",
+ "777",
+ "888",
+ "999",
+ "aaa",
+ "bbb",
+ "ccc",
+ "ddd",
+ "eee",
+ "fff",
+ "ggg",
+ "hhh",
+ "iii",
+ "jjj"
};
private MatrixCursor mDelegate;
@@ -79,7 +87,7 @@
row.add(NUM_COLUMN, rand.nextInt());
}
- mCursor = new PageViewCursor(mDelegate, 10, 5);
+ mCursor = new PageViewCursor(mDelegate, createArgs(10, 5));
}
@Test
@@ -94,7 +102,7 @@
@Test
public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() {
- mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5);
+ mCursor = new PageViewCursor(mDelegate, createArgs(ITEM_COUNT * 2, 5));
assertEquals(0, mCursor.getCount());
}
@@ -155,13 +163,13 @@
@Test
public void testCount_ZeroForEmptyCursor() {
- mCursor = new PageViewCursor(mDelegate, 0, 0);
+ mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
assertEquals(0, mCursor.getCount());
}
@Test
public void testIsBeforeFirst_TrueForEmptyCursor() {
- mCursor = new PageViewCursor(mDelegate, 0, 0);
+ mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
assertTrue(mCursor.isBeforeFirst());
}
@@ -175,7 +183,7 @@
@Test
public void testIsAfterLast_TrueForEmptyCursor() {
- mCursor = new PageViewCursor(mDelegate, 0, 0);
+ mCursor = new PageViewCursor(mDelegate, createArgs(0, 0));
assertTrue(mCursor.isAfterLast());
}
@@ -247,71 +255,131 @@
}
@Test
- public void testOffset_LimitOutOfBounds() {
- mCursor = new PageViewCursor(mDelegate, 5, 100);
+ public void testLimitOutOfBounds() {
+ mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
assertEquals(15, mCursor.getCount());
}
@Test
- public void testAutoPagedExtra() {
- mCursor = new PageViewCursor(mDelegate, 5, 100);
+ public void testOffsetOutOfBounds_EmptyResult() {
+ mCursor = new PageViewCursor(mDelegate, createArgs(100000, 100));
+ assertEquals(0, mCursor.getCount());
+ }
+
+ @Test
+ public void testAddsExtras() {
+ mCursor = new PageViewCursor(mDelegate, createArgs(5, 100));
assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED));
+ String[] honoredArgs = mCursor.getExtras()
+ .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+ assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+ assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
+ }
+
+ @Test
+ public void testAddsExtras_OnlyOffset() {
+ Bundle args = new Bundle();
+ args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+ mCursor = new PageViewCursor(mDelegate, args);
+ String[] honoredArgs = mCursor.getExtras()
+ .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+ assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+ assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
+ }
+
+ @Test
+ public void testAddsExtras_OnlyLimit() {
+ Bundle args = new Bundle();
+ args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+ mCursor = new PageViewCursor(mDelegate, args);
+ String[] honoredArgs = mCursor.getExtras()
+ .getStringArray(ContentResolver.EXTRA_HONORED_ARGS);
+ assertFalse(contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET));
+ assertTrue(contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT));
}
@Test
public void testGetWindow() {
- mCursor = new PageViewCursor(mDelegate, 5, 5);
+ mCursor = new PageViewCursor(mDelegate, createArgs(5, 5));
CursorWindow window = mCursor.getWindow();
assertEquals(5, window.getNumRows());
}
@Test
- public void testWrap() {
- Bundle queryArgs = new Bundle();
- queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
- queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
- Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+ public void testWraps() {
+ Bundle args = createArgs(5, 5);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
assertTrue(wrapped instanceof PageViewCursor);
assertEquals(5, wrapped.getCount());
}
@Test
- public void testWrap_NoOpWithoutPagingArgs() {
+ public void testWraps_NullExtras() {
+ Bundle args = createArgs(5, 5);
+ mDelegate.setExtras(null);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+ assertTrue(wrapped instanceof PageViewCursor);
+ assertEquals(5, wrapped.getCount());
+ }
+
+ @Test
+ public void testWraps_WithJustOffset() {
+ Bundle args = new Bundle();
+ args.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+ assertTrue(wrapped instanceof PageViewCursor);
+ assertEquals(15, wrapped.getCount());
+ }
+
+ @Test
+ public void testWraps_WithJustLimit() {
+ Bundle args = new Bundle();
+ args.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
+ assertTrue(wrapped instanceof PageViewCursor);
+ assertEquals(5, wrapped.getCount());
+ }
+
+ @Test
+ public void testNoWrap_WithoutPagingArgs() {
Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY);
assertTrue(mDelegate == wrapped);
}
@Test
- public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() {
+ public void testNoWrap_CursorsHasExistingPaging_ByTotalSize() {
Bundle extras = new Bundle();
extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5);
mDelegate.setExtras(extras);
- Bundle queryArgs = new Bundle();
- queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
- queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
- Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+ Bundle args = createArgs(5, 5);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
assertTrue(mDelegate == wrapped);
}
@Test
- public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() {
+ public void testNoWrap_CursorsHasExistingPaging_ByHonoredArgs() {
Bundle extras = new Bundle();
extras.putStringArray(
ContentResolver.EXTRA_HONORED_ARGS,
new String[] {
- ContentResolver.QUERY_ARG_OFFSET,
- ContentResolver.QUERY_ARG_LIMIT
+ ContentResolver.QUERY_ARG_OFFSET,
+ ContentResolver.QUERY_ARG_LIMIT
});
mDelegate.setExtras(extras);
- Bundle queryArgs = new Bundle();
- queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5);
- queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5);
- Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs);
+ Bundle args = createArgs(5, 5);
+ Cursor wrapped = PageViewCursor.wrap(mDelegate, args);
assertTrue(mDelegate == wrapped);
}
+ private static Bundle createArgs(int offset, int limit) {
+ Bundle args = new Bundle();
+ args.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
+ args.putInt(ContentResolver.QUERY_ARG_LIMIT, limit);
+ return args;
+ }
+
private void assertStringAt(int row, int column, String expected) {
mCursor.moveToPosition(row);
assertEquals(expected, mCursor.getString(column));
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 3fbf169..108585d 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -349,7 +349,6 @@
assertCanBeHandled(new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIFI_IP_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
- assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 7be71ee..8126d57 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -264,10 +264,15 @@
// the actual time spent blocked.
nsecs_t forgiveAmount = std::min(expectedDequeueDuration,
frame[FrameInfoIndex::DequeueBufferDuration]);
+ LOG_ALWAYS_FATAL_IF(forgiveAmount >= totalDuration,
+ "Impossible dequeue duration! dequeue duration reported %" PRId64
+ ", total duration %" PRId64, forgiveAmount, totalDuration);
totalDuration -= forgiveAmount;
}
}
+ LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
+ LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
// Keep the fast path as fast as possible.
if (CC_LIKELY(totalDuration < mFrameInterval)) {
mData->frameCounts[framebucket]++;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index e32fd63..1450ec9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -98,6 +98,7 @@
}
void TaskQueue::queueAtFront(RenderTask* task) {
+ LOG_ALWAYS_FATAL_IF(task->mNext || mHead == task, "Task is already in the queue!");
if (mTail) {
task->mNext = mHead;
mHead = task;
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 2e22132..611fdd1 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.MediaCasException.*;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -28,6 +29,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Singleton;
@@ -84,8 +86,6 @@
* sessionId of the descrambler can be retrieved by {@link MediaExtractor#getDrmInitData}
* and used to initialize a MediaDescrambler object for MediaCodec.
* <p>
- * TODO: determine exception handling schemes.
- * <p>
* <h3>Listeners</h3>
* <p>The app may register a listener to receive events from the CA system using
* method {@link #setEventListener}. The exact format of the event is scheme-specific
@@ -382,28 +382,22 @@
mEventHandler = new EventHandler(looper);
}
- /*
- * TODO: handle ServiceSpecificException from the IMediaCas
- * All Drm-specific failures will be thrown by mICas as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Send the private data for the CA system.
*
* @param data byte array of the private data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void setPrivateData(@NonNull byte[] data) {
+ public void setPrivateData(@NonNull byte[] data) throws MediaCasException {
validateInternalStates();
try {
mICas.setPrivateData(data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -416,14 +410,17 @@
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the program already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber) {
+ public byte[] openSession(int programNumber) throws MediaCasException {
validateInternalStates();
try {
return mICas.openSession(programNumber);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -438,14 +435,18 @@
*
* @return session id of the newly opened session.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if a session for the stream already exists.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public byte[] openSession(int programNumber, int elementaryPID) {
+ public byte[] openSession(int programNumber, int elementaryPID)
+ throws MediaCasException {
validateInternalStates();
try {
return mICas.openSessionForStream(programNumber, elementaryPID);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -457,14 +458,16 @@
*
* @param sessionId the session to be closed.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public void closeSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mICas.closeSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -476,17 +479,18 @@
* @param sessionId the session for which the private data is intended.
* @param data byte array of the private data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void setSessionPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.setSessionPrivateData(sessionId, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -500,19 +504,19 @@
* @param offset position within data where the ECM data begins.
* @param length length of the data (starting from offset).
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEcm(
- @NonNull byte[] sessionId, @NonNull byte[] data, int offset, int length) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data,
+ int offset, int length) throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEcm(sessionId, mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -526,13 +530,12 @@
* @param sessionId the session for which the ECM is intended.
* @param data byte array of the ECM data.
*
- * @throws IllegalStateException if the MediaCas instance is not valid,
- * or IllegalArgumentException if the session is not valid.
+ * @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data) {
+ public void processEcm(@NonNull byte[] sessionId, @NonNull byte[] data)
+ throws MediaCasException {
processEcm(sessionId, data, 0, data.length);
}
@@ -544,16 +547,18 @@
* @param length length of the data (starting from offset).
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data, int offset, int length) {
+ public void processEmm(@NonNull byte[] data, int offset, int length)
+ throws MediaCasException {
validateInternalStates();
try {
mCasData.set(data, offset, length);
mICas.processEmm(mCasData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -567,11 +572,10 @@
* @param data byte array of the EMM data.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: need to re-throw DRM-specific exceptions
- */
- public void processEmm(@NonNull byte[] data) {
+ public void processEmm(@NonNull byte[] data) throws MediaCasException {
processEmm(data, 0, data.length);
}
@@ -584,12 +588,17 @@
* @param data a byte array containing scheme-specific data for the event.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void sendEvent(int event, int arg, @Nullable byte[] data) {
+ public void sendEvent(int event, int arg, @Nullable byte[] data)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.sendEvent(event, arg, data);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -603,12 +612,16 @@
* specific.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- public void provision(@NonNull String provisionString) {
+ public void provision(@NonNull String provisionString) throws MediaCasException {
validateInternalStates();
try {
mICas.provision(provisionString);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -621,15 +634,17 @@
* @param refreshData private data associated with the refreshment.
*
* @throws IllegalStateException if the MediaCas instance is not valid.
+ * @throws MediaCasException for CAS-specific errors.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
- /*
- * TODO: define enums for refreshType
- */
- public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) {
+ public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData)
+ throws MediaCasException {
validateInternalStates();
try {
mICas.refreshEntitlements(refreshType, refreshData);
+ } catch (ServiceSpecificException e) {
+ MediaCasException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
index 1d5d3cd..485f6ee 100644
--- a/media/java/android/media/MediaCasException.java
+++ b/media/java/android/media/MediaCasException.java
@@ -16,11 +16,104 @@
package android.media;
+import android.os.ServiceSpecificException;
+
/**
* Base class for MediaCas exceptions
*/
public class MediaCasException extends Exception {
+
+ /** @hide */
+ public static final int DRM_ERROR_BASE = -2000;
+ /** @hide */
+ public static final int ERROR_DRM_UNKNOWN = DRM_ERROR_BASE;
+ /** @hide */
+ public static final int ERROR_DRM_NO_LICENSE = DRM_ERROR_BASE - 1;
+ /** @hide */
+ public static final int ERROR_DRM_LICENSE_EXPIRED = DRM_ERROR_BASE - 2;
+ /** @hide */
+ public static final int ERROR_DRM_SESSION_NOT_OPENED = DRM_ERROR_BASE - 3;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED = DRM_ERROR_BASE - 4;
+ /** @hide */
+ public static final int ERROR_DRM_DECRYPT = DRM_ERROR_BASE - 5;
+ /** @hide */
+ public static final int ERROR_DRM_CANNOT_HANDLE = DRM_ERROR_BASE - 6;
+ /** @hide */
+ public static final int ERROR_DRM_TAMPER_DETECTED = DRM_ERROR_BASE - 7;
+ /** @hide */
+ public static final int ERROR_DRM_NOT_PROVISIONED = DRM_ERROR_BASE - 8;
+ /** @hide */
+ public static final int ERROR_DRM_DEVICE_REVOKED = DRM_ERROR_BASE - 9;
+ /** @hide */
+ public static final int ERROR_DRM_RESOURCE_BUSY = DRM_ERROR_BASE - 10;
+ /** @hide */
+ public static final int ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_LAST_USED_ERRORCODE = DRM_ERROR_BASE - 11;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MAX = DRM_ERROR_BASE - 500;
+ /** @hide */
+ public static final int ERROR_DRM_VENDOR_MIN = DRM_ERROR_BASE - 999;
+
+ /** @hide */
public MediaCasException(String detailMessage) {
super(detailMessage);
}
+
+ static void throwExceptions(ServiceSpecificException e) throws MediaCasException {
+ if (e.errorCode == ERROR_DRM_NOT_PROVISIONED) {
+ throw new NotProvisionedException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_RESOURCE_BUSY) {
+ throw new ResourceBusyException(e.getMessage());
+ } else if (e.errorCode == ERROR_DRM_DEVICE_REVOKED) {
+ throw new DeniedByServerException(e.getMessage());
+ } else {
+ MediaCasStateException.throwExceptions(e);
+ }
+ }
+
+ /**
+ * Exception thrown when an attempt is made to construct a MediaCas object
+ * using a CA_system_id that is not supported by the device
+ */
+ public static final class UnsupportedCasException extends MediaCasException {
+ /** @hide */
+ public UnsupportedCasException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * before it's provisioned successfully.
+ */
+ public static final class NotProvisionedException extends MediaCasException {
+ /** @hide */
+ public NotProvisionedException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when the provisioning server or key server denies a
+ * license for a device.
+ */
+ public static final class DeniedByServerException extends MediaCasException {
+ /** @hide */
+ public DeniedByServerException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Exception thrown when an operation on a MediaCas object is attempted
+ * and hardware resources are not available, due to being in use.
+ */
+ public static final class ResourceBusyException extends MediaCasException {
+ /** @hide */
+ public ResourceBusyException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
}
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
new file mode 100644
index 0000000..cf05c29
--- /dev/null
+++ b/media/java/android/media/MediaCasStateException.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ServiceSpecificException;
+
+import static android.media.MediaCasException.*;
+
+/**
+ * Base class for MediaCas runtime exceptions
+ */
+public class MediaCasStateException extends IllegalStateException {
+ private final int mErrorCode;
+ private final String mDiagnosticInfo;
+
+ /** @hide */
+ public MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
+ super(msg);
+ mErrorCode = err;
+ mDiagnosticInfo = diagnosticInfo;
+ }
+
+ static void throwExceptions(ServiceSpecificException e) {
+ String diagnosticInfo = "";
+ switch (e.errorCode) {
+ case ERROR_DRM_UNKNOWN:
+ diagnosticInfo = "General CAS error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ diagnosticInfo = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ diagnosticInfo = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ diagnosticInfo = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ diagnosticInfo = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ diagnosticInfo = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ diagnosticInfo = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ diagnosticInfo = "Tamper detected";
+ break;
+ default:
+ diagnosticInfo = "Unknown CAS state exception";
+ break;
+ }
+ throw new MediaCasStateException(e.errorCode, e.getMessage(),
+ String.format("%s (err=%d)", diagnosticInfo, e.errorCode));
+ }
+
+ /**
+ * Retrieve the associated error code
+ *
+ * @hide
+ */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Retrieve a developer-readable diagnostic information string
+ * associated with the exception. Do not show this to end-users,
+ * since this string will not be localized or generally comprehensible
+ * to end-users.
+ */
+ @NonNull
+ public String getDiagnosticInfo() {
+ return mDiagnosticInfo;
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 13a22b4..e628d18 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1878,9 +1878,7 @@
* @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the
* component as an encoder.
* @param descrambler Specify a descrambler object to facilitate secure
- * descrambling of the media data. descrambler must not be
- * null if this method is used. For non-secure codecs, use
- * {@link #configure} and with null crypto parameter.
+ * descrambling of the media data, or null for non-secure codecs.
* @throws IllegalArgumentException if the surface has been released (or is invalid),
* or the format is unacceptable (e.g. missing a mandatory key),
* or the flags are not set properly
@@ -1891,8 +1889,9 @@
*/
public void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
- @ConfigureFlag int flags, @NonNull MediaDescrambler descrambler) {
- configure(format, surface, null, descrambler.getBinder(), flags);
+ @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
+ configure(format, surface, null,
+ descrambler != null ? descrambler.getBinder() : null, flags);
}
private void configure(
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
index f5eede8..2dd1097 100644
--- a/media/java/android/media/MediaDescrambler.java
+++ b/media/java/android/media/MediaDescrambler.java
@@ -17,10 +17,12 @@
package android.media;
import android.annotation.NonNull;
+import android.media.MediaCasException.UnsupportedCasException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.nio.ByteBuffer;
@@ -54,13 +56,13 @@
/**
* Class for parceling descrambling parameters over IDescrambler binder.
*/
+ // This class currently is not used by Java binder. descramble() goes through
+ // jni to use shared memory. However, the parcelable is still required for AIDL.
static class DescrambleInfo implements Parcelable {
private DescrambleInfo() {
- // TODO: implement
}
private DescrambleInfo(Parcel in) {
- // TODO: disable
}
@Override
@@ -70,7 +72,6 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- // TODO: implement
}
public static final Parcelable.Creator<DescrambleInfo> CREATOR
@@ -112,13 +113,6 @@
return mIDescrambler.asBinder();
}
- /*
- * TODO: handle ServiceSpecificException from the mIDescrambler
- * All Drm-specific failures will be thrown by mIDescrambler as
- * ServiceSpecificException exception with Drm error code.
- * These need to be re-thrown as crypto exceptions.
- */
-
/**
* Query if the scrambling scheme requires the use of a secure decoder
* to decode data of the given mime type.
@@ -150,14 +144,16 @@
* @param sessionId the MediaCas sessionId to associate with this
* MediaDescrambler instance.
*
- * @throws IllegalStateException if the descrambler instance is not valid,
- * or IllegalArgumentException if the sessionId is not valid.
+ * @throws IllegalStateException if the descrambler instance is not valid.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final void setMediaCasSession(@NonNull byte[] sessionId) {
validateInternalStates();
try {
mIDescrambler.setMediaCasSession(sessionId);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -179,9 +175,7 @@
* values indicating errors.
*
* @throws IllegalStateException if the descrambler instance is not valid.
- */
- /*
- * TODO: throw DRM-specific exception if decrambling is failing.
+ * @throws MediaCasStateException for CAS-specific state exceptions.
*/
public final int descramble(
@NonNull ByteBuffer srcBuf, int srcPos, ByteBuffer dstBuf, int dstPos,
@@ -208,12 +202,17 @@
"Invalid CryptoInfo: key array is invalid!");
}
- return native_descramble(
- cryptoInfo.key[0],
- cryptoInfo.numSubSamples,
- cryptoInfo.numBytesOfClearData,
- cryptoInfo.numBytesOfEncryptedData,
- srcBuf, srcPos, dstBuf, dstPos);
+ try {
+ return native_descramble(
+ cryptoInfo.key[0],
+ cryptoInfo.numSubSamples,
+ cryptoInfo.numBytesOfClearData,
+ cryptoInfo.numBytesOfEncryptedData,
+ srcBuf, srcPos, dstBuf, dstPos);
+ } catch (ServiceSpecificException e) {
+ MediaCasStateException.throwExceptions(e);
+ }
+ return -1;
}
public final void release() {
diff --git a/media/java/android/media/UnsupportedCasException.java b/media/java/android/media/UnsupportedCasException.java
deleted file mode 100644
index 3167637..0000000
--- a/media/java/android/media/UnsupportedCasException.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-/**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
- */
-public final class UnsupportedCasException extends MediaCasException {
- public UnsupportedCasException(String detailMessage) {
- super(detailMessage);
- }
-}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index bee3f52..f10f442 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -77,13 +77,19 @@
/**
* Set this flag on the session to indicate that it can handle media button
* events.
+ * @deprecated This flag is no longer used. All media sessions are expected to handle media
+ * button events now.
*/
+ @Deprecated
public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
/**
* Set this flag on the session to indicate that it handles transport
* control commands through its {@link Callback}.
+ * @deprecated This flag is no longer used. All media sessions are expected to handle transport
+ * controls now.
*/
+ @Deprecated
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
/**
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 957c582..762f0c0 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.SystemApi;
+import android.hardware.tv.input.V1_0.Constants;
import android.media.AudioManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,16 +38,16 @@
static final String TAG = "TvInputHardwareInfo";
// Match hardware/libhardware/include/hardware/tv_input.h
- public static final int TV_INPUT_TYPE_OTHER_HARDWARE = 1;
- public static final int TV_INPUT_TYPE_TUNER = 2;
- public static final int TV_INPUT_TYPE_COMPOSITE = 3;
- public static final int TV_INPUT_TYPE_SVIDEO = 4;
- public static final int TV_INPUT_TYPE_SCART = 5;
- public static final int TV_INPUT_TYPE_COMPONENT = 6;
- public static final int TV_INPUT_TYPE_VGA = 7;
- public static final int TV_INPUT_TYPE_DVI = 8;
- public static final int TV_INPUT_TYPE_HDMI = 9;
- public static final int TV_INPUT_TYPE_DISPLAY_PORT = 10;
+ public static final int TV_INPUT_TYPE_OTHER_HARDWARE = Constants.TV_INPUT_TYPE_OTHER;
+ public static final int TV_INPUT_TYPE_TUNER = Constants.TV_INPUT_TYPE_TUNER;
+ public static final int TV_INPUT_TYPE_COMPOSITE = Constants.TV_INPUT_TYPE_COMPOSITE;
+ public static final int TV_INPUT_TYPE_SVIDEO = Constants.TV_INPUT_TYPE_SVIDEO;
+ public static final int TV_INPUT_TYPE_SCART = Constants.TV_INPUT_TYPE_SCART;
+ public static final int TV_INPUT_TYPE_COMPONENT = Constants.TV_INPUT_TYPE_COMPONENT;
+ public static final int TV_INPUT_TYPE_VGA = Constants.TV_INPUT_TYPE_VGA;
+ public static final int TV_INPUT_TYPE_DVI = Constants.TV_INPUT_TYPE_DVI;
+ public static final int TV_INPUT_TYPE_HDMI = Constants.TV_INPUT_TYPE_HDMI;
+ public static final int TV_INPUT_TYPE_DISPLAY_PORT = Constants.TV_INPUT_TYPE_DISPLAY_PORT;
/** @hide */
@Retention(SOURCE)
@@ -58,17 +59,20 @@
/**
* The hardware is unsure about the connection status or does not support cable detection.
*/
- public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0;
+ public static final int CABLE_CONNECTION_STATUS_UNKNOWN =
+ Constants.CABLE_CONNECTION_STATUS_UNKNOWN;
/**
* Cable is connected to the hardware.
*/
- public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1;
+ public static final int CABLE_CONNECTION_STATUS_CONNECTED =
+ Constants.CABLE_CONNECTION_STATUS_CONNECTED;
/**
* Cable is disconnected to the hardware.
*/
- public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2;
+ public static final int CABLE_CONNECTION_STATUS_DISCONNECTED =
+ Constants.CABLE_CONNECTION_STATUS_DISCONNECTED;
public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR =
new Parcelable.Creator<TvInputHardwareInfo>() {
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index 7585664..f031dbb 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -129,7 +129,7 @@
mMem = mDealer->allocate(neededSize);
}
-ssize_t JDescrambler::descramble(
+Status JDescrambler::descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
@@ -137,7 +137,8 @@
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset) {
+ jint dstOffset,
+ ssize_t *result) {
// TODO: IDescrambler::descramble() is re-entrant, however because we
// only have 1 shared mem buffer, we can only do 1 descramble at a time.
// Concurrency might be improved by allowing on-demand allocation of up
@@ -159,16 +160,16 @@
info.dstPtr = NULL;
info.dstOffset = 0;
- int32_t result;
- binder::Status status = mDescrambler->descramble(info, &result);
+ int32_t descrambleResult;
+ Status status = mDescrambler->descramble(info, &descrambleResult);
- if (!status.isOk() || result > totalLength) {
- return -1;
+ if (status.isOk()) {
+ *result = (descrambleResult <= totalLength) ? descrambleResult : -1;
+ if (*result > 0) {
+ memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *result);
+ }
}
- if (result > 0) {
- memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), result);
- }
- return result;
+ return status;
}
} // namespace android
@@ -251,11 +252,45 @@
numBytesOfClearData = NULL;
}
+ if (totalSize < 0) {
+ delete[] subSamples;
+ return -1;
+ }
+
*outSubSamples = subSamples;
return totalSize;
}
+static jthrowable createServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ if (env->ExceptionCheck()) {
+ ALOGW("Discarding pending exception");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/os/ServiceSpecificException"));
+ CHECK(clazz.get() != NULL);
+
+ const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+ CHECK(ctor != NULL);
+
+ ScopedLocalRef<jstring> msgObj(
+ env, env->NewStringUTF(msg != NULL ?
+ msg : String8::format("Error %#x", serviceSpecificError)));
+
+ return (jthrowable)env->NewObject(
+ clazz.get(), ctor, serviceSpecificError, msgObj.get());
+}
+
+static void throwServiceSpecificException(
+ JNIEnv *env, int serviceSpecificError, const char *msg) {
+ jthrowable exception = createServiceSpecificException(env, serviceSpecificError, msg);
+ env->Throw(exception);
+}
+
static jint android_media_MediaDescrambler_native_descramble(
JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
@@ -290,11 +325,11 @@
env, dstBuf, dstOffset, totalLength, &dstPtr, &dstArray);
}
}
-
+ Status status;
if (err == OK) {
- result = descrambler->descramble(
+ status = descrambler->descramble(
key, numSubSamples, totalLength, subSamples,
- srcPtr, srcOffset, dstPtr, dstOffset);
+ srcPtr, srcOffset, dstPtr, dstOffset, &result);
}
delete[] subSamples;
@@ -304,6 +339,51 @@
if (dstArray != NULL) {
env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
}
+
+ if (!status.isOk()) {
+ switch (status.exceptionCode()) {
+ case Status::EX_SECURITY:
+ jniThrowException(env, "java/lang/SecurityException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_BAD_PARCELABLE:
+ jniThrowException(env, "java/lang/BadParcelableException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_ARGUMENT:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NULL_POINTER:
+ jniThrowException(env, "java/lang/NullPointerException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_ILLEGAL_STATE:
+ jniThrowException(env, "java/lang/IllegalStateException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_NETWORK_MAIN_THREAD:
+ jniThrowException(env, "java/lang/NetworkOnMainThreadException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_UNSUPPORTED_OPERATION:
+ jniThrowException(env, "java/lang/UnsupportedOperationException",
+ status.exceptionMessage());
+ break;
+ case Status::EX_SERVICE_SPECIFIC:
+ throwServiceSpecificException(env, status.serviceSpecificErrorCode(),
+ status.exceptionMessage());
+ break;
+ default:
+ {
+ String8 msg;
+ msg.appendFormat("Unknown exception code: %d, msg: %s",
+ status.exceptionCode(), status.exceptionMessage().string());
+ jniThrowException(env, "java/lang/RuntimeException", msg.string());
+ break;
+ }
+ }
+ }
return result;
}
diff --git a/media/jni/android_media_MediaDescrambler.h b/media/jni/android_media_MediaDescrambler.h
index e944a90..aeef05e 100644
--- a/media/jni/android_media_MediaDescrambler.h
+++ b/media/jni/android_media_MediaDescrambler.h
@@ -19,6 +19,7 @@
#include "jni.h"
+#include <binder/Status.h>
#include <media/cas/DescramblerAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/Mutex.h>
@@ -31,11 +32,12 @@
class IDescrambler;
};
using namespace media;
+using binder::Status;
struct JDescrambler : public RefBase {
JDescrambler(JNIEnv *env, jobject descramberBinderObj);
- ssize_t descramble(
+ Status descramble(
jbyte key,
size_t numSubSamples,
ssize_t totalLength,
@@ -43,7 +45,8 @@
const void *srcPtr,
jint srcOffset,
void *dstPtr,
- jint dstOffset);
+ jint dstOffset,
+ ssize_t *result);
protected:
virtual ~JDescrambler();
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 8df194c..e450283 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -27,7 +27,9 @@
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
- <application android:label="@string/app_name" >
+ <application
+ android:label="@string/app_name"
+ android:directBootAware="true">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
<intent-filter>
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 0280f26..1f86f8b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -526,9 +526,13 @@
// the given ScanResult. This is used for showing that a given AP
// (ScanResult) is available via a Passpoint provider (provider friendly
// name).
- WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
- if (config != null) {
- accessPoint.update(config);
+ try {
+ WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
+ if (config != null) {
+ accessPoint.update(config);
+ }
+ } catch (UnsupportedOperationException e) {
+ // Passpoint not supported on the device.
}
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 14bb02d..a8629f8 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -51,7 +51,7 @@
<bool name="def_wifi_on">false</bool>
<!-- 0 == never, 1 == only when plugged in, 2 == always -->
<integer name="def_wifi_sleep_policy">2</integer>
- <bool name="def_wifi_wakeup_enabled">false</bool>
+ <bool name="def_wifi_wakeup_enabled">true</bool>
<bool name="def_networks_available_notification_on">true</bool>
<bool name="def_backup_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1a752f9..0d0ddf2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2798,7 +2798,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 142;
+ private static final int SETTINGS_VERSION = 143;
private final int mUserId;
@@ -3343,10 +3343,30 @@
currentVersion = 142;
}
+ if (currentVersion == 142) {
+ // Version 142: Set a default value for Wi-Fi wakeup feature.
+ if (userId == UserHandle.USER_SYSTEM) {
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ Setting currentSetting = globalSettings.getSettingLocked(
+ Settings.Global.WIFI_WAKEUP_ENABLED);
+ if (currentSetting.isNull()) {
+ globalSettings.insertSettingLocked(
+ Settings.Global.WIFI_WAKEUP_ENABLED,
+ getContext().getResources().getBoolean(
+ R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 143;
+ }
+
if (currentVersion != newVersion) {
Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
+ newVersion + " left it at "
- + currentVersion + " instead; this is probably a bug", new Throwable());
+ + currentVersion +
+ " instead; this is probably a bug. Did you update SETTINGS_VERSION?",
+ new Throwable());
if (DEBUG) {
throw new RuntimeException("db upgrade error");
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 6bfab78..2519e02 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -113,6 +113,7 @@
<uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 1ebfbad..635c96f 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -38,7 +38,6 @@
android-support-v17-leanback
LOCAL_STATIC_JAVA_LIBRARIES := \
- framework-protos \
SystemUI-tags \
SystemUI-proto
diff --git a/packages/SystemUI/plugin/Android.mk b/packages/SystemUI/plugin/Android.mk
index 05ee6b2..e22dddb 100644
--- a/packages/SystemUI/plugin/Android.mk
+++ b/packages/SystemUI/plugin/Android.mk
@@ -36,4 +36,6 @@
LOCAL_JAVA_LIBRARIES := SystemUIPluginLib
+LOCAL_PROGUARD_ENABLED := disabled
+
include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5b20716..bf17e38 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1878,6 +1878,15 @@
<!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
<string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
+ <!-- PiP section of the tuner. [CHAR LIMIT=NONE] -->
+ <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
+
+ <!-- PiP minimize title. [CHAR LIMIT=NONE]-->
+ <string name="pip_minimize_title" translatable="false">Minimize</string>
+
+ <!-- PiP minimize description. [CHAR LIMIT=NONE] -->
+ <string name="pip_minimize_description" translatable="false">Drag or fling the PIP to the edges of the screen to minimize it.</string>
+
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index bc3edd5..908fb20 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -122,6 +122,18 @@
</PreferenceScreen>
<PreferenceScreen
+ android:key="picture_in_picture"
+ android:title="@string/picture_in_picture">
+
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="pip_minimize"
+ android:title="@string/pip_minimize_title"
+ android:summary="@string/pip_minimize_description"
+ sysui:defValue="false" />
+
+ </PreferenceScreen>
+
+ <PreferenceScreen
android:key="doze"
android:title="@string/tuner_doze">
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b689a850..a0f491f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -37,8 +37,10 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tuner.TunerService;
import java.io.PrintWriter;
@@ -46,9 +48,11 @@
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
*/
-public class PipTouchHandler {
+public class PipTouchHandler implements TunerService.Tunable {
private static final String TAG = "PipTouchHandler";
+ private static final String TUNER_KEY_MINIMIZE = "pip_minimize";
+
// These values are used for metrics and should never change
private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0;
private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1;
@@ -97,6 +101,9 @@
}
};
+ // Allow the PIP to be dragged to the edge of the screen to be minimized.
+ private boolean mEnableMinimize = false;
+
// Behaviour states
private boolean mIsMenuVisible;
private boolean mIsMinimized;
@@ -169,6 +176,9 @@
mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
+ // Register any tuner settings changes
+ Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_MINIMIZE);
+
// Register the listener for input consumer touch events
inputConsumerController.setTouchListener(this::handleTouchEvent);
inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
@@ -189,6 +199,20 @@
}
}
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (newValue == null) {
+ // Reset back to default
+ mEnableMinimize = false;
+ return;
+ }
+ switch (key) {
+ case TUNER_KEY_MINIMIZE:
+ mEnableMinimize = Integer.parseInt(newValue) != 0;
+ break;
+ }
+ }
+
public void onConfigurationChanged() {
mMotionHelper.onConfigurationChanged();
mMotionHelper.synchronizePinnedStackBounds();
@@ -368,6 +392,9 @@
* Sets the minimized state.
*/
void setMinimizedStateInternal(boolean isMinimized) {
+ if (!mEnableMinimize) {
+ return;
+ }
setMinimizedState(isMinimized, false /* fromController */);
}
@@ -375,6 +402,9 @@
* Sets the minimized state.
*/
void setMinimizedState(boolean isMinimized, boolean fromController) {
+ if (!mEnableMinimize) {
+ return;
+ }
if (mIsMinimized != isMinimized) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED,
isMinimized);
@@ -483,7 +513,7 @@
final PointF lastDelta = touchState.getLastTouchDelta();
float left = mTmpBounds.left + lastDelta.x;
float top = mTmpBounds.top + lastDelta.y;
- if (!touchState.allowDraggingOffscreen()) {
+ if (!touchState.allowDraggingOffscreen() || !mEnableMinimize) {
left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left));
}
if (ENABLE_DISMISS_DRAG_TO_EDGE) {
@@ -550,10 +580,10 @@
final float velocity = PointF.length(vel.x, vel.y);
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
- final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
- final boolean isFlingToBot = !isHorizontal && mMovementWithinDismiss && vel.y > 0;
- final boolean isFlingToEdge = isHorizontal && mMovementWithinMinimize
- && (onLeft ? vel.x < 0 : vel.x > 0);
+ final boolean isFlingToBot = isFling
+ && !isHorizontal && mMovementWithinDismiss && vel.y > 0;
+ final boolean isFlingToEdge = isFling && isHorizontal && mMovementWithinMinimize
+ && (mStartedOnLeft ? vel.x < 0 : vel.x > 0);
if (ENABLE_DISMISS_DRAG_TO_EDGE
&& (mMotionHelper.shouldDismissPip() || isFlingToBot)) {
@@ -563,7 +593,8 @@
MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
METRIC_VALUE_DISMISSED_BY_DRAG);
return true;
- } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
+ } else if (mEnableMinimize &&
+ !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
// Pip should be minimized
setMinimizedStateInternal(true);
if (mMenuController.isMenuVisible()) {
@@ -631,6 +662,7 @@
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
pw.println(innerPrefix + "mEnableDragToDismiss=" + ENABLE_DISMISS_DRAG_TO_TARGET);
+ pw.println(innerPrefix + "mEnableMinimize=" + mEnableMinimize);
mSnapAlgorithm.dump(pw, innerPrefix);
mTouchState.dump(pw, innerPrefix);
mMotionHelper.dump(pw, innerPrefix);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index aa0fcbd..be16266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -82,6 +82,8 @@
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
mSignalClusterView = reinflateSignalCluster(mStatusBar);
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView);
+ // Default to showing until we know otherwise.
+ showSystemIconArea(false);
}
@Override
@@ -119,6 +121,8 @@
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
+ // Default to showing until we know otherwise.
+ showNotificationIconArea(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index bfe55bc..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -987,16 +987,19 @@
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
- CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;
+ CollapsedStatusBarFragment statusBarFragment =
+ (CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanel);
mStatusBarView.setScrimController(mScrimController);
setAreThereNotifications();
+ checkBarModes();
}).getFragmentManager()
.beginTransaction()
- .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
+ .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
+ CollapsedStatusBarFragment.TAG)
.commit();
Dependency.get(StatusBarIconController.class).addIconGroup(
new IconManager((ViewGroup) mKeyguardStatusBar.findViewById(R.id.statusIcons)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index a9acda3..6ddbffc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -16,6 +16,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -104,6 +105,6 @@
fragment.disable(0, 0, false);
- Mockito.verify(mNotificationAreaInner).setVisibility(eq(View.VISIBLE));
+ Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 4d78350..3d1c251 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -19,15 +19,10 @@
import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN;
import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
-import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
-import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
-import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
-import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
import static com.android.server.autofill.Helper.DEBUG;
import static com.android.server.autofill.Helper.VERBOSE;
-import static com.android.server.autofill.Helper.findValue;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,32 +30,23 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.app.assist.AssistStructure.WindowNode;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
-import android.metrics.LogMaker;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Parcelable;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
-import android.service.autofill.SaveInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.LocalLog;
@@ -68,21 +54,16 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
-import android.view.autofill.IAutofillWindowPresenter;
+
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.IResultReceiver;
import com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Map;
-import java.util.Map.Entry;
/**
* Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
@@ -93,13 +74,12 @@
private static final String TAG = "AutofillManagerServiceImpl";
- private static final int MSG_SERVICE_SAVE = 1;
+ static final int MSG_SERVICE_SAVE = 1;
private final int mUserId;
private final Context mContext;
private final Object mLock;
private final AutoFillUI mUi;
- private final MetricsLogger mMetricsLogger = new MetricsLogger();
private RemoteCallbackList<IAutoFillManagerClient> mClients;
private AutofillServiceInfo mInfo;
@@ -358,8 +338,9 @@
private Session createSessionByTokenLocked(@NonNull IBinder activityToken,
@Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback,
int flags, @NonNull String packageName) {
- final Session newSession = new Session(mContext, activityToken,
- windowToken, appCallbackToken, hasCallback, flags, packageName);
+ final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
+ activityToken, windowToken, appCallbackToken, hasCallback, flags,
+ mInfo.getServiceInfo().getComponentName(), packageName);
mSessions.put(activityToken, newSession);
/*
@@ -400,6 +381,10 @@
session.updateLocked(autofillId, virtualBounds, value, flags);
}
+ void removeSessionLocked(IBinder activityToken) {
+ mSessions.remove(activityToken);
+ }
+
private void handleSessionSave(IBinder activityToken) {
synchronized (mLock) {
final Session session = mSessions.get(activityToken);
@@ -423,6 +408,25 @@
mSessions.clear();
}
+ void disableSelf() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final String autoFillService = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
+ if (mInfo.getServiceInfo().getComponentName().equals(
+ ComponentName.unflattenFromString(autoFillService))) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ CharSequence getServiceLabel() {
+ return mInfo.getServiceInfo().loadLabel(mContext.getPackageManager());
+ }
+
void dumpLocked(String prefix, PrintWriter pw) {
final String prefix2 = prefix + " ";
@@ -495,804 +499,4 @@
+ ", component=" + (mInfo != null
? mInfo.getServiceInfo().getComponentName() : null) + "]";
}
-
- /**
- * State for a given view with a AutofillId.
- *
- * <p>This class holds state about a view and calls its listener when the fill UI is ready to
- * be displayed for the view.
- */
- static final class ViewState {
- interface Listener {
- /**
- * Called when the fill UI is ready to be shown for this view.
- */
- void onFillReady(FillResponse fillResponse, AutofillId focusedId,
- @Nullable AutofillValue value);
- }
-
- final AutofillId mId;
- private final Listener mListener;
- // TODO(b/33197203): would not need a reference to response and session if it was an inner
- // class of Session...
- private final Session mSession;
- // TODO(b/33197203): encapsulate access so it's not called by UI
- FillResponse mResponse;
- Intent mAuthIntent;
-
- private AutofillValue mAutofillValue;
-
- // Bounds if a virtual view, null otherwise
- private Rect mVirtualBounds;
-
- private boolean mValueUpdated;
-
- ViewState(Session session, AutofillId id, Listener listener) {
- mSession = session;
- mId = id;
- mListener = listener;
- }
-
- /**
- * Response should only be set once.
- */
- void setResponse(FillResponse response) {
- mResponse = response;
- maybeCallOnFillReady();
- }
-
- /**
- * Used when a {@link FillResponse} requires authentication to be unlocked.
- */
- void setResponse(FillResponse response, Intent authIntent) {
- mAuthIntent = authIntent;
- setResponse(response);
- }
-
- CharSequence getServiceName() {
- return mSession.getServiceName();
- }
-
- // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
- // it can change the value and update the UI; similarly, should replace code that
- // directly sets mAutoFilLValue to use encapsulation.
- void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
- if (autofillValue != null) {
- mAutofillValue = autofillValue;
- }
- if (virtualBounds != null) {
- mVirtualBounds = virtualBounds;
- }
-
- maybeCallOnFillReady();
- }
-
- /**
- * Calls {@link
- * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
- * fill UI is ready to be displayed (i.e. when response and bounds are set).
- */
- void maybeCallOnFillReady() {
- if (mResponse != null && (mResponse.getAuthentication() != null
- || mResponse.getDatasets() != null)) {
- mListener.onFillReady(mResponse, mId, mAutofillValue);
- }
- }
-
- @Override
- public String toString() {
- return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
- + ", updated = " + mValueUpdated + "]";
- }
-
- void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("id:" ); pw.println(mId);
- pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
- pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
- pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
- pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
- }
- }
-
- /**
- * A session for a given activity.
- *
- * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
- * of the current {@link ViewState} to display the appropriate UI.
- *
- * <p>Although the autofill requests and callbacks are stateless from the service's point of
- * view, we need to keep state in the framework side for cases such as authentication. For
- * example, when service return a {@link FillResponse} that contains all the fields needed
- * to fill the activity but it requires authentication first, that response need to be held
- * until the user authenticates or it times out.
- */
- // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
- // - On all authentication scenarios.
- // - When user does not interact back after a while.
- // - When service is unbound.
- final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
- AutoFillUI.AutoFillUiCallback {
- private final IBinder mActivityToken;
- private final IBinder mWindowToken;
-
- /** Package name of the app that is auto-filled */
- @NonNull private final String mPackageName;
-
- @GuardedBy("mLock")
- private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
-
- @GuardedBy("mLock")
- @Nullable
- private ViewState mCurrentViewState;
-
- private final IAutoFillManagerClient mClient;
-
- @GuardedBy("mLock")
- RemoteFillService mRemoteFillService;
-
- // TODO(b/33197203): Get a response per view instead of per activity.
- @GuardedBy("mLock")
- private FillResponse mCurrentResponse;
-
- /**
- * Used to remember which {@link Dataset} filled the session.
- */
- // TODO(b/33197203): might need more than one once we support partitions
- @GuardedBy("mLock")
- private Dataset mAutoFilledDataset;
-
- /**
- * Assist structure sent by the app; it will be updated (sanitized, change values for save)
- * before sent to {@link AutofillService}.
- */
- @GuardedBy("mLock")
- private AssistStructure mStructure;
-
- /**
- * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
- */
- private boolean mHasCallback;
-
- /**
- * Flags used to start the session.
- */
- private int mFlags;
- private Session(@NonNull Context context, @NonNull IBinder activityToken,
- @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
- int flags, @NonNull String packageName) {
- mRemoteFillService = new RemoteFillService(context,
- mInfo.getServiceInfo().getComponentName(), mUserId, this);
- mActivityToken = activityToken;
- mWindowToken = windowToken;
- mHasCallback = hasCallback;
- mFlags = flags;
- mPackageName = packageName;
-
- mClient = IAutoFillManagerClient.Stub.asInterface(client);
- try {
- client.linkToDeath(() -> {
- if (DEBUG) {
- Slog.d(TAG, "app binder died");
- }
-
- removeSelf();
- }, 0);
- } catch (RemoteException e) {
- Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
- }
-
- mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
- }
-
- // FillServiceCallbacks
- @Override
- public void onFillRequestSuccess(@Nullable FillResponse response,
- @NonNull String servicePackageName) {
- if (response == null) {
- // Nothing to be done, but need to notify client.
- notifyUnavailableToClient();
- removeSelf();
- return;
- }
-
- if ((response.getDatasets() == null || response.getDatasets().isEmpty())
- && response.getAuthentication() == null) {
- // Response is "empty" from an UI point of view, need to notify client.
- notifyUnavailableToClient();
- }
- synchronized (mLock) {
- processResponseLocked(response);
- }
-
- LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
- .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
- response.getDatasets() == null ? 0 : response.getDatasets().size())
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
- servicePackageName);
- mMetricsLogger.write(log);
- }
-
- // FillServiceCallbacks
- @Override
- public void onFillRequestFailure(@Nullable CharSequence message,
- @NonNull String servicePackageName) {
- LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
- .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
- servicePackageName);
- mMetricsLogger.write(log);
-
- getUiForShowing().showError(message);
- removeSelf();
- }
-
- // FillServiceCallbacks
- @Override
- public void onSaveRequestSuccess(@NonNull String servicePackageName) {
- LogMaker log = (new LogMaker(
- MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
- .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
- servicePackageName);
- mMetricsLogger.write(log);
-
- // Nothing left to do...
- removeSelf();
- }
-
- // FillServiceCallbacks
- @Override
- public void onSaveRequestFailure(@Nullable CharSequence message,
- @NonNull String servicePackageName) {
- LogMaker log = (new LogMaker(
- MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
- .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
- servicePackageName);
- mMetricsLogger.write(log);
-
- getUiForShowing().showError(message);
- removeSelf();
- }
-
- // FillServiceCallbacks
- @Override
- public void authenticate(IntentSender intent) {
- final Intent fillInIntent;
- synchronized (mLock) {
- fillInIntent = createAuthFillInIntent(mStructure);
- }
- mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
- }
-
- // FillServiceCallbacks
- @Override
- public void onDisableSelf() {
- final long identity = Binder.clearCallingIdentity();
- try {
- final String autoFillService = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE, mUserId);
- if (mInfo.getServiceInfo().getComponentName().equals(
- ComponentName.unflattenFromString(autoFillService))) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- synchronized (mLock) {
- removeSelfLocked();
- }
- }
-
- // FillServiceCallbacks
- @Override
- public void onServiceDied(RemoteFillService service) {
- // TODO(b/33197203): implement
- }
-
- // AutoFillUiCallback
- @Override
- public void fill(Dataset dataset) {
- mHandlerCaller.getHandler().post(() -> autoFill(dataset));
- }
-
- // AutoFillUiCallback
- @Override
- public void save() {
- mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken)
- .sendToTarget();
- }
-
- // AutoFillUiCallback
- @Override
- public void cancelSave() {
- mHandlerCaller.getHandler().post(() -> removeSelf());
- }
-
- // AutoFillUiCallback
- @Override
- public void requestShowFillUi(AutofillId id, int width, int height,
- IAutofillWindowPresenter presenter) {
- try {
- mClient.requestShowFillUi(mWindowToken, id, width, height,
- mCurrentViewState.mVirtualBounds, presenter);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error requesting to show fill UI", e);
- }
- }
-
- // AutoFillUiCallback
- @Override
- public void requestHideFillUi(AutofillId id) {
- try {
- mClient.requestHideFillUi(mWindowToken, id);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error requesting to hide fill UI", e);
- }
- }
-
- public void setAuthenticationResultLocked(Bundle data) {
- if (mCurrentResponse == null || data == null) {
- removeSelf();
- } else {
- Parcelable result = data.getParcelable(
- AutofillManager.EXTRA_AUTHENTICATION_RESULT);
- if (result instanceof FillResponse) {
- mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_AUTHENTICATED,
- mPackageName);
-
- mCurrentResponse = (FillResponse) result;
- processResponseLocked(mCurrentResponse);
- } else if (result instanceof Dataset) {
- Dataset dataset = (Dataset) result;
- final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
- if (index >= 0) {
- mCurrentResponse.getDatasets().set(index, dataset);
- autoFill(dataset);
- }
- }
- }
- }
-
- public void setHasCallback(boolean hasIt) {
- mHasCallback = hasIt;
- }
-
- /**
- * Shows the save UI, when session can be saved.
- *
- * @return {@code true} if session is done, or {@code false} if it's pending user action.
- */
- public boolean showSaveLocked() {
- if (mStructure == null) {
- Slog.wtf(TAG, "showSaveLocked(): no mStructure");
- return true;
- }
- if (mCurrentResponse == null) {
- // Happens when the activity / session was finished before the service replied, or
- // when the service cannot autofill it (and returned a null response).
- if (DEBUG) {
- Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
- }
- return true;
- }
- final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
- if (DEBUG) {
- Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
- }
-
- /*
- * The Save dialog is only shown if all conditions below are met:
- *
- * - saveInfo is not null
- * - autofillValue of all required ids is not null
- * - autofillValue of at least one id (required or optional) has changed.
- */
-
- if (saveInfo == null) {
- return true;
- }
-
- final AutofillId[] requiredIds = saveInfo.getRequiredIds();
- if (requiredIds == null || requiredIds.length == 0) {
- Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
- return true;
- }
-
- boolean allRequiredAreNotEmpty = true;
- boolean atLeastOneChanged = false;
- for (int i = 0; i < requiredIds.length; i++) {
- final AutofillId id = requiredIds[i];
- final ViewState state = mViewStates.get(id);
- if (state == null || state.mAutofillValue == null
- || state.mAutofillValue.isEmpty()) {
- final ViewNode node = findViewNodeByIdLocked(id);
- if (node == null) {
- Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
- allRequiredAreNotEmpty = false;
- break;
- }
- final AutofillValue initialValue = node.getAutofillValue();
- if (initialValue == null || initialValue.isEmpty()) {
- if (DEBUG) {
- Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id );
- }
- allRequiredAreNotEmpty = false;
- break;
- }
- }
- if (state.mValueUpdated) {
- final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
- if (!state.mAutofillValue.equals(filledValue)) {
- if (DEBUG) {
- Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
- + filledValue + " => " + state.mAutofillValue);
- }
- atLeastOneChanged = true;
- }
- } else {
- if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
- if (DEBUG) {
- Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
- + state.mAutofillValue);
- }
- allRequiredAreNotEmpty = false;
- break;
-
- }
- }
- }
-
- if (allRequiredAreNotEmpty) {
- if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) {
- for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
- final AutofillId id = saveInfo.getOptionalIds()[i];
- final ViewState state = mViewStates.get(id);
- if (state != null && state.mAutofillValue != null && state.mValueUpdated) {
- final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
- if (!state.mAutofillValue.equals(filledValue)) {
- if (DEBUG) {
- Slog.d(TAG, "finishSessionLocked(): found a change on optional "
- + id + ": " + filledValue + " => "
- + state.mAutofillValue);
- }
- atLeastOneChanged = true;
- break;
- }
- }
- }
- }
- if (atLeastOneChanged) {
- getUiForShowing().showSaveUi(
- mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()),
- saveInfo, mPackageName);
- return false;
- }
- }
- // Nothing changed...
- if (DEBUG) {
- Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
- + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
- + ", atLeastOneChanged=" + atLeastOneChanged);
- }
- return true;
- }
-
- /**
- * Calls service when user requested save.
- */
- private void callSaveLocked() {
- if (DEBUG) {
- Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
- }
-
- final Bundle extras = this.mCurrentResponse.getExtras();
-
- for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
- final AutofillValue value = entry.getValue().mAutofillValue;
- if (value == null) {
- if (VERBOSE) {
- Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
- }
- continue;
- }
- final AutofillId id = entry.getKey();
- final ViewNode node = findViewNodeByIdLocked(id);
- if (node == null) {
- Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
- continue;
- }
- if (VERBOSE) {
- Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
- }
-
- node.updateAutofillValue(value);
- }
-
- // Sanitize structure before it's sent to service.
- mStructure.sanitizeForParceling(false);
-
- if (VERBOSE) {
- Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
- mStructure.dump();
- }
-
- mRemoteFillService.onSaveRequest(mStructure, extras);
- }
-
- void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
- if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
- // TODO(b/33197203): ignoring because we don't support partitions yet
- Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
- return;
- }
-
- ViewState viewState = mViewStates.get(id);
- if (viewState == null) {
- viewState = new ViewState(this, id, this);
- mViewStates.put(id, viewState);
- }
-
- if ((flags & FLAG_START_SESSION) != 0) {
- // View is triggering autofill.
- mCurrentViewState = viewState;
- viewState.update(value, virtualBounds);
- return;
- }
-
- if ((flags & FLAG_VALUE_CHANGED) != 0) {
- if (value != null && !value.equals(viewState.mAutofillValue)) {
- viewState.mValueUpdated = true;
-
- // Must check if this update was caused by autofilling the view, in which
- // case we just update the value, but not the UI.
- if (mAutoFilledDataset != null) {
- final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
- if (value.equals(filledValue)) {
- viewState.mAutofillValue = value;
- return;
- }
- }
-
- // Change value
- viewState.mAutofillValue = value;
-
- // Update the chooser UI
- if (value.isText()) {
- getUiForShowing().filterFillUi(value.getTextValue().toString());
- } else {
- getUiForShowing().filterFillUi(null);
- }
- }
-
- return;
- }
-
- if ((flags & FLAG_VIEW_ENTERED) != 0) {
- // Remove the UI if the ViewState has changed.
- if (mCurrentViewState != viewState) {
- mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null);
- mCurrentViewState = viewState;
- }
-
- // If the ViewState is ready to be displayed, onReady() will be called.
- viewState.update(value, virtualBounds);
-
- // TODO(b/33197203): Remove when there is a response per activity.
- if (mCurrentResponse != null) {
- viewState.setResponse(mCurrentResponse);
- }
-
- return;
- }
-
- if ((flags & FLAG_VIEW_EXITED) != 0) {
- if (mCurrentViewState == viewState) {
- mUi.hideFillUi(viewState.mId);
- mCurrentViewState = null;
- }
- return;
- }
-
- Slog.w(TAG, "updateLocked(): unknown flags " + flags);
- }
-
- @Override
- public void onFillReady(FillResponse response, AutofillId filledId,
- @Nullable AutofillValue value) {
- String filterText = null;
- if (value != null && value.isText()) {
- filterText = value.getTextValue().toString();
- }
-
- getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
- }
-
- private void notifyUnavailableToClient() {
- if (mCurrentViewState == null) {
- // TODO(b/33197203): temporary sanity check; should never happen
- Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
- return;
- }
- if (!mHasCallback) return;
- try {
- mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
- + " id=" + mCurrentViewState.mId, e);
- }
- }
-
- private void processResponseLocked(FillResponse response) {
- if (DEBUG) {
- Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
- + "):" + response);
- }
-
- if (mCurrentViewState == null) {
- // TODO(b/33197203): temporary sanity check; should never happen
- Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null");
- return;
- }
-
- mCurrentResponse = response;
-
- if (mCurrentResponse.getAuthentication() != null) {
- // Handle authentication.
- final Intent fillInIntent = createAuthFillInIntent(mStructure);
- mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
- return;
- }
-
- if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
- && response.getDatasets().size() == 1) {
- Slog.d(TAG, "autofilling manual request directly");
- autoFill(response.getDatasets().get(0));
- return;
- }
-
- mCurrentViewState.setResponse(mCurrentResponse);
- }
-
- void autoFill(Dataset dataset) {
- synchronized (mLock) {
- mAutoFilledDataset = dataset;
-
- // Autofill it directly...
- if (dataset.getAuthentication() == null) {
- autoFillApp(dataset);
- return;
- }
-
- // ...or handle authentication.
- Intent fillInIntent = createAuthFillInIntent(mStructure);
- startAuthentication(dataset.getAuthentication(), fillInIntent);
- }
- }
-
- CharSequence getServiceName() {
- return AutofillManagerServiceImpl.this.getServiceName();
- }
-
- private Intent createAuthFillInIntent(AssistStructure structure) {
- Intent fillInIntent = new Intent();
- fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
- return fillInIntent;
- }
-
- private void startAuthentication(IntentSender intent, Intent fillInIntent) {
- try {
- mClient.authenticate(intent, fillInIntent);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error launching auth intent", e);
- }
- }
-
- void dumpLocked(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
- pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
- pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
- pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
- pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
- pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
- final String prefix2 = prefix + " ";
- for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
- pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
- entry.getValue().dump(prefix2, pw);
- }
- if (VERBOSE) {
- pw.print(prefix); pw.print("mStructure: " );
- // TODO(b/33197203): add method do dump AssistStructure on pw
- if (mStructure != null) {
- pw.println("look at logcat" );
- mStructure.dump(); // dumps to logcat
- } else {
- pw.println("null");
- }
- }
- pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
- mRemoteFillService.dump(prefix, pw);
- }
-
- void autoFillApp(Dataset dataset) {
- synchronized (mLock) {
- try {
- if (DEBUG) {
- Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
- }
- mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
- } catch (RemoteException e) {
- Slog.w(TAG, "Error autofilling activity: " + e);
- }
- }
- }
-
- private AutoFillUI getUiForShowing() {
- synchronized (mLock) {
- mUi.setCallback(this);
- return mUi;
- }
- }
-
- private ViewNode findViewNodeByIdLocked(AutofillId id) {
- final int size = mStructure.getWindowNodeCount();
- for (int i = 0; i < size; i++) {
- final WindowNode window = mStructure.getWindowNodeAt(i);
- final ViewNode root = window.getRootViewNode();
- if (id.equals(root.getAutofillId())) {
- return root;
- }
- final ViewNode child = findViewNodeByIdLocked(root, id);
- if (child != null) {
- return child;
- }
- }
- return null;
- }
-
- private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
- final int childrenSize = parent.getChildCount();
- if (childrenSize > 0) {
- for (int i = 0; i < childrenSize; i++) {
- final ViewNode child = parent.getChildAt(i);
- if (id.equals(child.getAutofillId())) {
- return child;
- }
- final ViewNode grandChild = findViewNodeByIdLocked(child, id);
- if (grandChild != null && id.equals(grandChild.getAutofillId())) {
- return grandChild;
- }
- }
- }
- return null;
- }
-
- private void destroyLocked() {
- mRemoteFillService.destroy();
- mUi.setCallback(null);
- mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
- mPackageName);
- }
-
- void removeSelf() {
- synchronized (mLock) {
- removeSelfLocked();
- }
- }
-
- private void removeSelfLocked() {
- if (VERBOSE) {
- Slog.v(TAG, "removeSelfLocked()");
- }
- destroyLocked();
- mSessions.remove(mActivityToken);
- }
- }
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
new file mode 100644
index 0000000..1093e9e
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -0,0 +1,764 @@
+/*
+ * 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.autofill;
+
+import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
+import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
+import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
+import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
+import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
+
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.VERBOSE;
+import static com.android.server.autofill.Helper.findValue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.graphics.Rect;
+import android.metrics.LogMaker;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.autofill.IAutofillWindowPresenter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.HandlerCaller;
+import com.android.server.autofill.ui.AutoFillUI;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A session for a given activity.
+ *
+ * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
+ * of the current {@link ViewState} to display the appropriate UI.
+ *
+ * <p>Although the autofill requests and callbacks are stateless from the service's point of
+ * view, we need to keep state in the framework side for cases such as authentication. For
+ * example, when service return a {@link FillResponse} that contains all the fields needed
+ * to fill the activity but it requires authentication first, that response need to be held
+ * until the user authenticates or it times out.
+ */
+// TODO(b/33197203): make sure sessions are removed (and tested by CTS):
+// - On all authentication scenarios.
+// - When user does not interact back after a while.
+// - When service is unbound.
+final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
+ AutoFillUI.AutoFillUiCallback {
+ private static final String TAG = "AutofillSession";
+
+ private final AutofillManagerServiceImpl mService;
+ private final IBinder mActivityToken;
+ private final IBinder mWindowToken;
+ private final HandlerCaller mHandlerCaller;
+ private final Object mLock;
+ private final AutoFillUI mUi;
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+ /** Package name of the app that is auto-filled */
+ @NonNull private final String mPackageName;
+
+ @GuardedBy("mLock")
+ private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ @Nullable
+ private ViewState mCurrentViewState;
+
+ private final IAutoFillManagerClient mClient;
+
+ @GuardedBy("mLock")
+ RemoteFillService mRemoteFillService;
+
+ // TODO(b/33197203): Get a response per view instead of per activity.
+ @GuardedBy("mLock")
+ private FillResponse mCurrentResponse;
+
+ /**
+ * Used to remember which {@link Dataset} filled the session.
+ */
+ // TODO(b/33197203): might need more than one once we support partitions
+ @GuardedBy("mLock")
+ private Dataset mAutoFilledDataset;
+
+ /**
+ * Assist structure sent by the app; it will be updated (sanitized, change values for save)
+ * before sent to {@link AutofillService}.
+ */
+ @GuardedBy("mLock") AssistStructure mStructure;
+
+ /**
+ * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
+ */
+ private boolean mHasCallback;
+
+ /**
+ * Flags used to start the session.
+ */
+ int mFlags;
+
+ Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
+ @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
+ @NonNull Object lock, @NonNull IBinder activityToken,
+ @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
+ int flags, @NonNull ComponentName componentName, @NonNull String packageName) {
+ mService = service;
+ mLock = lock;
+ mUi = ui;
+ mHandlerCaller = handlerCaller;
+ mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
+ mActivityToken = activityToken;
+ mWindowToken = windowToken;
+ mHasCallback = hasCallback;
+ mPackageName = packageName;
+ mFlags = flags;
+
+ mClient = IAutoFillManagerClient.Stub.asInterface(client);
+ try {
+ client.linkToDeath(() -> {
+ if (DEBUG) {
+ Slog.d(TAG, "app binder died");
+ }
+
+ removeSelf();
+ }, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
+ }
+
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onFillRequestSuccess(@Nullable FillResponse response,
+ @NonNull String servicePackageName) {
+ if (response == null) {
+ // Nothing to be done, but need to notify client.
+ notifyUnavailableToClient();
+ removeSelf();
+ return;
+ }
+
+ if ((response.getDatasets() == null || response.getDatasets().isEmpty())
+ && response.getAuthentication() == null) {
+ // Response is "empty" from an UI point of view, need to notify client.
+ notifyUnavailableToClient();
+ }
+ synchronized (mLock) {
+ processResponseLocked(response);
+ }
+
+ LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+ .setType(MetricsEvent.TYPE_SUCCESS)
+ .setPackageName(mPackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+ response.getDatasets() == null ? 0 : response.getDatasets().size())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
+ servicePackageName);
+ mMetricsLogger.write(log);
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onFillRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName) {
+ LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+ .setType(MetricsEvent.TYPE_FAILURE)
+ .setPackageName(mPackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ mMetricsLogger.write(log);
+
+ getUiForShowing().showError(message);
+ removeSelf();
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+ LogMaker log = (new LogMaker(
+ MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
+ .setType(MetricsEvent.TYPE_SUCCESS)
+ .setPackageName(mPackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ mMetricsLogger.write(log);
+
+ // Nothing left to do...
+ removeSelf();
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onSaveRequestFailure(@Nullable CharSequence message,
+ @NonNull String servicePackageName) {
+ LogMaker log = (new LogMaker(
+ MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
+ .setType(MetricsEvent.TYPE_FAILURE)
+ .setPackageName(mPackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ mMetricsLogger.write(log);
+
+ getUiForShowing().showError(message);
+ removeSelf();
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void authenticate(IntentSender intent) {
+ final Intent fillInIntent;
+ synchronized (mLock) {
+ fillInIntent = createAuthFillInIntent(mStructure);
+ }
+ mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onDisableSelf() {
+ mService.disableSelf();
+ synchronized (mLock) {
+ removeSelfLocked();
+ }
+ }
+
+ // FillServiceCallbacks
+ @Override
+ public void onServiceDied(RemoteFillService service) {
+ // TODO(b/33197203): implement
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void fill(Dataset dataset) {
+ mHandlerCaller.getHandler().post(() -> autoFill(dataset));
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void save() {
+ mHandlerCaller.getHandler()
+ .obtainMessage(AutofillManagerServiceImpl.MSG_SERVICE_SAVE, mActivityToken)
+ .sendToTarget();
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void cancelSave() {
+ mHandlerCaller.getHandler().post(() -> removeSelf());
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestShowFillUi(AutofillId id, int width, int height,
+ IAutofillWindowPresenter presenter) {
+ try {
+ mClient.requestShowFillUi(mWindowToken, id, width, height,
+ mCurrentViewState.mVirtualBounds, presenter);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to show fill UI", e);
+ }
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestHideFillUi(AutofillId id) {
+ try {
+ mClient.requestHideFillUi(mWindowToken, id);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting to hide fill UI", e);
+ }
+ }
+
+ public void setAuthenticationResultLocked(Bundle data) {
+ if (mCurrentResponse == null || data == null) {
+ removeSelf();
+ } else {
+ Parcelable result = data.getParcelable(
+ AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+ if (result instanceof FillResponse) {
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
+
+ mCurrentResponse = (FillResponse) result;
+ processResponseLocked(mCurrentResponse);
+ } else if (result instanceof Dataset) {
+ Dataset dataset = (Dataset) result;
+ final int index = mCurrentResponse.getDatasets().indexOf(mAutoFilledDataset);
+ if (index >= 0) {
+ mCurrentResponse.getDatasets().set(index, dataset);
+ autoFill(dataset);
+ }
+ }
+ }
+ }
+
+ public void setHasCallback(boolean hasIt) {
+ mHasCallback = hasIt;
+ }
+
+ /**
+ * Shows the save UI, when session can be saved.
+ *
+ * @return {@code true} if session is done, or {@code false} if it's pending user action.
+ */
+ public boolean showSaveLocked() {
+ if (mStructure == null) {
+ Slog.wtf(TAG, "showSaveLocked(): no mStructure");
+ return true;
+ }
+ if (mCurrentResponse == null) {
+ // Happens when the activity / session was finished before the service replied, or
+ // when the service cannot autofill it (and returned a null response).
+ if (DEBUG) {
+ Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
+ }
+ return true;
+ }
+ final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
+ if (DEBUG) {
+ Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
+ }
+
+ /*
+ * The Save dialog is only shown if all conditions below are met:
+ *
+ * - saveInfo is not null
+ * - autofillValue of all required ids is not null
+ * - autofillValue of at least one id (required or optional) has changed.
+ */
+
+ if (saveInfo == null) {
+ return true;
+ }
+
+ final AutofillId[] requiredIds = saveInfo.getRequiredIds();
+ if (requiredIds == null || requiredIds.length == 0) {
+ Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
+ return true;
+ }
+
+ boolean allRequiredAreNotEmpty = true;
+ boolean atLeastOneChanged = false;
+ for (int i = 0; i < requiredIds.length; i++) {
+ final AutofillId id = requiredIds[i];
+ final ViewState state = mViewStates.get(id);
+ if (state == null || state.mAutofillValue == null
+ || state.mAutofillValue.isEmpty()) {
+ final ViewNode node = findViewNodeByIdLocked(id);
+ if (node == null) {
+ Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
+ allRequiredAreNotEmpty = false;
+ break;
+ }
+ final AutofillValue initialValue = node.getAutofillValue();
+ if (initialValue == null || initialValue.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id );
+ }
+ allRequiredAreNotEmpty = false;
+ break;
+ }
+ }
+ if (state.mValueUpdated) {
+ final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+ if (!state.mAutofillValue.equals(filledValue)) {
+ if (DEBUG) {
+ Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
+ + filledValue + " => " + state.mAutofillValue);
+ }
+ atLeastOneChanged = true;
+ }
+ } else {
+ if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
+ + state.mAutofillValue);
+ }
+ allRequiredAreNotEmpty = false;
+ break;
+
+ }
+ }
+ }
+
+ if (allRequiredAreNotEmpty) {
+ if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) {
+ for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
+ final AutofillId id = saveInfo.getOptionalIds()[i];
+ final ViewState state = mViewStates.get(id);
+ if (state != null && state.mAutofillValue != null && state.mValueUpdated) {
+ final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+ if (!state.mAutofillValue.equals(filledValue)) {
+ if (DEBUG) {
+ Slog.d(TAG, "finishSessionLocked(): found a change on optional "
+ + id + ": " + filledValue + " => "
+ + state.mAutofillValue);
+ }
+ atLeastOneChanged = true;
+ break;
+ }
+ }
+ }
+ }
+ if (atLeastOneChanged) {
+ getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName);
+ return false;
+ }
+ }
+ // Nothing changed...
+ if (DEBUG) {
+ Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
+ + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
+ + ", atLeastOneChanged=" + atLeastOneChanged);
+ }
+ return true;
+ }
+
+ /**
+ * Calls service when user requested save.
+ */
+ void callSaveLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
+ }
+
+ final Bundle extras = this.mCurrentResponse.getExtras();
+
+ for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
+ final AutofillValue value = entry.getValue().mAutofillValue;
+ if (value == null) {
+ if (VERBOSE) {
+ Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
+ }
+ continue;
+ }
+ final AutofillId id = entry.getKey();
+ final ViewNode node = findViewNodeByIdLocked(id);
+ if (node == null) {
+ Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
+ continue;
+ }
+ if (VERBOSE) {
+ Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
+ }
+
+ node.updateAutofillValue(value);
+ }
+
+ // Sanitize structure before it's sent to service.
+ mStructure.sanitizeForParceling(false);
+
+ if (VERBOSE) {
+ Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
+ mStructure.dump();
+ }
+
+ mRemoteFillService.onSaveRequest(mStructure, extras);
+ }
+
+ void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int flags) {
+ if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
+ // TODO(b/33197203): ignoring because we don't support partitions yet
+ Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
+ return;
+ }
+
+ ViewState viewState = mViewStates.get(id);
+ if (viewState == null) {
+ viewState = new ViewState(this, id, this);
+ mViewStates.put(id, viewState);
+ }
+
+ if ((flags & FLAG_START_SESSION) != 0) {
+ // View is triggering autofill.
+ mCurrentViewState = viewState;
+ viewState.update(value, virtualBounds);
+ return;
+ }
+
+ if ((flags & FLAG_VALUE_CHANGED) != 0) {
+ if (value != null && !value.equals(viewState.mAutofillValue)) {
+ viewState.mValueUpdated = true;
+
+ // Must check if this update was caused by autofilling the view, in which
+ // case we just update the value, but not the UI.
+ if (mAutoFilledDataset != null) {
+ final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
+ if (value.equals(filledValue)) {
+ viewState.mAutofillValue = value;
+ return;
+ }
+ }
+
+ // Change value
+ viewState.mAutofillValue = value;
+
+ // Update the chooser UI
+ if (value.isText()) {
+ getUiForShowing().filterFillUi(value.getTextValue().toString());
+ } else {
+ getUiForShowing().filterFillUi(null);
+ }
+ }
+
+ return;
+ }
+
+ if ((flags & FLAG_VIEW_ENTERED) != 0) {
+ // Remove the UI if the ViewState has changed.
+ if (mCurrentViewState != viewState) {
+ mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null);
+ mCurrentViewState = viewState;
+ }
+
+ // If the ViewState is ready to be displayed, onReady() will be called.
+ viewState.update(value, virtualBounds);
+
+ // TODO(b/33197203): Remove when there is a response per activity.
+ if (mCurrentResponse != null) {
+ viewState.setResponse(mCurrentResponse);
+ }
+
+ return;
+ }
+
+ if ((flags & FLAG_VIEW_EXITED) != 0) {
+ if (mCurrentViewState == viewState) {
+ mUi.hideFillUi(viewState.mId);
+ mCurrentViewState = null;
+ }
+ return;
+ }
+
+ Slog.w(TAG, "updateLocked(): unknown flags " + flags);
+ }
+
+ @Override
+ public void onFillReady(FillResponse response, AutofillId filledId,
+ @Nullable AutofillValue value) {
+ String filterText = null;
+ if (value != null && value.isText()) {
+ filterText = value.getTextValue().toString();
+ }
+
+ getUiForShowing().showFillUi(filledId, response, filterText, mPackageName);
+ }
+
+ private void notifyUnavailableToClient() {
+ if (mCurrentViewState == null) {
+ // TODO(b/33197203): temporary sanity check; should never happen
+ Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
+ return;
+ }
+ if (!mHasCallback) return;
+ try {
+ mClient.notifyNoFillUi(mWindowToken, mCurrentViewState.mId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying client no fill UI: windowToken=" + mWindowToken
+ + " id=" + mCurrentViewState.mId, e);
+ }
+ }
+
+ private void processResponseLocked(FillResponse response) {
+ if (DEBUG) {
+ Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
+ + "):" + response);
+ }
+
+ if (mCurrentViewState == null) {
+ // TODO(b/33197203): temporary sanity check; should never happen
+ Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null");
+ return;
+ }
+
+ mCurrentResponse = response;
+
+ if (mCurrentResponse.getAuthentication() != null) {
+ // Handle authentication.
+ final Intent fillInIntent = createAuthFillInIntent(mStructure);
+ mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
+ return;
+ }
+
+ if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
+ && response.getDatasets().size() == 1) {
+ Slog.d(TAG, "autofilling manual request directly");
+ autoFill(response.getDatasets().get(0));
+ return;
+ }
+
+ mCurrentViewState.setResponse(mCurrentResponse);
+ }
+
+ void autoFill(Dataset dataset) {
+ synchronized (mLock) {
+ mAutoFilledDataset = dataset;
+
+ // Autofill it directly...
+ if (dataset.getAuthentication() == null) {
+ autoFillApp(dataset);
+ return;
+ }
+
+ // ...or handle authentication.
+ Intent fillInIntent = createAuthFillInIntent(mStructure);
+ startAuthentication(dataset.getAuthentication(), fillInIntent);
+ }
+ }
+
+ CharSequence getServiceName() {
+ return mService.getServiceName();
+ }
+
+ private Intent createAuthFillInIntent(AssistStructure structure) {
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
+ return fillInIntent;
+ }
+
+ private void startAuthentication(IntentSender intent, Intent fillInIntent) {
+ try {
+ mClient.authenticate(intent, fillInIntent);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error launching auth intent", e);
+ }
+ }
+
+ void dumpLocked(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+ pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
+ pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
+ pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
+ pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
+ pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
+ final String prefix2 = prefix + " ";
+ for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
+ pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
+ entry.getValue().dump(prefix2, pw);
+ }
+ if (VERBOSE) {
+ pw.print(prefix); pw.print("mStructure: " );
+ // TODO(b/33197203): add method do dump AssistStructure on pw
+ if (mStructure != null) {
+ pw.println("look at logcat" );
+ mStructure.dump(); // dumps to logcat
+ } else {
+ pw.println("null");
+ }
+ }
+ pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
+ mRemoteFillService.dump(prefix, pw);
+ }
+
+ void autoFillApp(Dataset dataset) {
+ synchronized (mLock) {
+ try {
+ if (DEBUG) {
+ Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+ }
+ mClient.autofill(mWindowToken, dataset.getFieldIds(), dataset.getFieldValues());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error autofilling activity: " + e);
+ }
+ }
+ }
+
+ private AutoFillUI getUiForShowing() {
+ synchronized (mLock) {
+ mUi.setCallback(this);
+ return mUi;
+ }
+ }
+
+ private ViewNode findViewNodeByIdLocked(AutofillId id) {
+ final int size = mStructure.getWindowNodeCount();
+ for (int i = 0; i < size; i++) {
+ final WindowNode window = mStructure.getWindowNodeAt(i);
+ final ViewNode root = window.getRootViewNode();
+ if (id.equals(root.getAutofillId())) {
+ return root;
+ }
+ final ViewNode child = findViewNodeByIdLocked(root, id);
+ if (child != null) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
+ final int childrenSize = parent.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ final ViewNode child = parent.getChildAt(i);
+ if (id.equals(child.getAutofillId())) {
+ return child;
+ }
+ final ViewNode grandChild = findViewNodeByIdLocked(child, id);
+ if (grandChild != null && id.equals(grandChild.getAutofillId())) {
+ return grandChild;
+ }
+ }
+ }
+ return null;
+ }
+
+ void destroyLocked() {
+ mRemoteFillService.destroy();
+ mUi.setCallback(null);
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+ }
+
+ void removeSelf() {
+ synchronized (mLock) {
+ removeSelfLocked();
+ }
+ }
+
+ void removeSelfLocked() {
+ if (VERBOSE) {
+ Slog.v(TAG, "removeSelfLocked()");
+ }
+ destroyLocked();
+ mService.removeSessionLocked(mActivityToken);
+ }
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
new file mode 100644
index 0000000..d31dcfd
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -0,0 +1,125 @@
+/*
+ * 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.autofill;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.service.autofill.FillResponse;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import java.io.PrintWriter;
+
+/**
+ * State for a given view with a AutofillId.
+ *
+ * <p>This class holds state about a view and calls its listener when the fill UI is ready to
+ * be displayed for the view.
+ */
+final class ViewState {
+ interface Listener {
+ /**
+ * Called when the fill UI is ready to be shown for this view.
+ */
+ void onFillReady(FillResponse fillResponse, AutofillId focusedId,
+ @Nullable AutofillValue value);
+ }
+
+ final AutofillId mId;
+ private final Listener mListener;
+ // TODO(b/33197203): would not need a reference to response and session if it was an inner
+ // class of Session...
+ private final Session mSession;
+ private FillResponse mResponse;
+ private Intent mAuthIntent;
+
+ // TODO(b/33197203): encapsulate access so it's not called by UI
+ AutofillValue mAutofillValue;
+
+ // TODO(b/33197203): encapsulate access so it's not called by UI
+ // Bounds if a virtual view, null otherwise
+ Rect mVirtualBounds;
+
+ boolean mValueUpdated;
+
+ ViewState(Session session, AutofillId id, Listener listener) {
+ mSession = session;
+ mId = id;
+ mListener = listener;
+ }
+
+ /**
+ * Response should only be set once.
+ */
+ void setResponse(FillResponse response) {
+ mResponse = response;
+ maybeCallOnFillReady();
+ }
+
+ /**
+ * Used when a {@link FillResponse} requires authentication to be unlocked.
+ */
+ void setResponse(FillResponse response, Intent authIntent) {
+ mAuthIntent = authIntent;
+ setResponse(response);
+ }
+
+ CharSequence getServiceName() {
+ return mSession.getServiceName();
+ }
+
+ // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
+ // it can change the value and update the UI; similarly, should replace code that
+ // directly sets mAutoFilLValue to use encapsulation.
+ void update(@Nullable AutofillValue autofillValue, @Nullable Rect virtualBounds) {
+ if (autofillValue != null) {
+ mAutofillValue = autofillValue;
+ }
+ if (virtualBounds != null) {
+ mVirtualBounds = virtualBounds;
+ }
+
+ maybeCallOnFillReady();
+ }
+
+ /**
+ * Calls {@link
+ * Listener#onFillReady(FillResponse, AutofillId, AutofillValue)} if the
+ * fill UI is ready to be displayed (i.e. when response and bounds are set).
+ */
+ void maybeCallOnFillReady() {
+ if (mResponse != null && (mResponse.getAuthentication() != null
+ || mResponse.getDatasets() != null)) {
+ mListener.onFillReady(mResponse, mId, mAutofillValue);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mVirtualBounds
+ + ", updated = " + mValueUpdated + "]";
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix); pw.print("id:" ); pw.println(mId);
+ pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
+ pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
+ pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
+ pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index 5b627d9..1a3bb35 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -88,9 +88,12 @@
final String serviceLabel = getRecommendationServiceLabel(serviceInfo, pm);
final ComponentName useOpenWifiNetworksActivity =
findUseOpenWifiNetworksActivity(serviceInfo);
+ final String networkAvailableNotificationChannelId =
+ getNetworkAvailableNotificationChannelId(serviceInfo);
appDataList.add(
new NetworkScorerAppData(serviceInfo.applicationInfo.uid,
- serviceComponentName, serviceLabel, useOpenWifiNetworksActivity));
+ serviceComponentName, serviceLabel, useOpenWifiNetworksActivity,
+ networkAvailableNotificationChannelId));
} else {
if (VERBOSE) Log.v(TAG, serviceInfo.packageName
+ " is NOT a valid scorer/recommender.");
@@ -145,6 +148,20 @@
return null;
}
+ @Nullable
+ private static String getNetworkAvailableNotificationChannelId(ServiceInfo serviceInfo) {
+ if (serviceInfo.metaData == null) {
+ if (DEBUG) {
+ Log.d(TAG, "No metadata found on " + serviceInfo.getComponentName());
+ }
+ return null;
+ }
+
+ return serviceInfo.metaData.getString(
+ NetworkScoreManager.NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA);
+ }
+
+
/**
* Get the application to use for scoring networks.
*
diff --git a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
index c3b7e15..6380da5 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerBackupHelper.java
@@ -100,18 +100,20 @@
Account account = null;
AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- for (Account[] accountsPerType : accounts.accountCache.values()) {
- for (Account accountPerType : accountsPerType) {
- if (accountDigest.equals(PackageUtils.computeSha256Digest(
- accountPerType.name.getBytes()))) {
- account = accountPerType;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ for (Account[] accountsPerType : accounts.accountCache.values()) {
+ for (Account accountPerType : accountsPerType) {
+ if (accountDigest.equals(PackageUtils.computeSha256Digest(
+ accountPerType.name.getBytes()))) {
+ account = accountPerType;
+ break;
+ }
+ }
+ if (account != null) {
break;
}
}
- if (account != null) {
- break;
- }
}
}
if (account == null) {
@@ -141,49 +143,52 @@
public byte[] backupAccountAccessPermissions(int userId) {
final AccountManagerService.UserAccounts accounts = mAccountManagerService
.getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
- .findAllAccountGrants();
- if (allAccountGrants.isEmpty()) {
- return null;
- }
- try {
- ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
- final XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_PERMISSIONS);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ List<Pair<String, Integer>> allAccountGrants = accounts.accountsDb
+ .findAllAccountGrants();
+ if (allAccountGrants.isEmpty()) {
+ return null;
+ }
+ try {
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PERMISSIONS);
- PackageManager packageManager = mAccountManagerService.mContext.getPackageManager();
- for (Pair<String, Integer> grant : allAccountGrants) {
- final String accountName = grant.first;
- final int uid = grant.second;
+ PackageManager packageManager = mAccountManagerService.mContext
+ .getPackageManager();
+ for (Pair<String, Integer> grant : allAccountGrants) {
+ final String accountName = grant.first;
+ final int uid = grant.second;
- final String[] packageNames = packageManager.getPackagesForUid(uid);
- if (packageNames == null) {
- continue;
- }
+ final String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (packageNames == null) {
+ continue;
+ }
- for (String packageName : packageNames) {
- String digest = PackageUtils.computePackageCertSha256Digest(
- packageManager, packageName, userId);
- if (digest != null) {
- serializer.startTag(null, TAG_PERMISSION);
- serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
- PackageUtils.computeSha256Digest(accountName.getBytes()));
- serializer.attribute(null, ATTR_PACKAGE, packageName);
- serializer.attribute(null, ATTR_DIGEST, digest);
- serializer.endTag(null, TAG_PERMISSION);
+ for (String packageName : packageNames) {
+ String digest = PackageUtils.computePackageCertSha256Digest(
+ packageManager, packageName, userId);
+ if (digest != null) {
+ serializer.startTag(null, TAG_PERMISSION);
+ serializer.attribute(null, ATTR_ACCOUNT_SHA_256,
+ PackageUtils.computeSha256Digest(accountName.getBytes()));
+ serializer.attribute(null, ATTR_PACKAGE, packageName);
+ serializer.attribute(null, ATTR_DIGEST, digest);
+ serializer.endTag(null, TAG_PERMISSION);
+ }
}
}
+ serializer.endTag(null, TAG_PERMISSIONS);
+ serializer.endDocument();
+ serializer.flush();
+ return dataStream.toByteArray();
+ } catch (IOException e) {
+ Log.e(TAG, "Error backing up account access grants", e);
+ return null;
}
- serializer.endTag(null, TAG_PERMISSIONS);
- serializer.endDocument();
- serializer.flush();
- return dataStream.toByteArray();
- } catch (IOException e) {
- Log.e(TAG, "Error backing up account access grants", e);
- return null;
}
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 8e3e3ea..79be3e6 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -201,17 +201,19 @@
private final HashMap<Account, Integer> signinRequiredNotificationIds =
new HashMap<Account, Integer>();
final Object cacheLock = new Object();
+ final Object dbLock = new Object();
/** protected by the {@link #cacheLock} */
- final HashMap<String, Account[]> accountCache =
- new LinkedHashMap<>();
+ final HashMap<String, Account[]> accountCache = new LinkedHashMap<>();
/** protected by the {@link #cacheLock} */
private final Map<Account, Map<String, String>> userDataCache = new HashMap<>();
/** protected by the {@link #cacheLock} */
private final Map<Account, Map<String, String>> authTokenCache = new HashMap<>();
/** protected by the {@link #cacheLock} */
private final TokenCache accountTokenCaches = new TokenCache();
+ /** protected by the {@link #cacheLock} */
+ private final Map<Account, Map<String, Integer>> visibilityCache = new HashMap<>();
- /** protected by the {@link #mReceiversForType}
+ /** protected by the {@link #mReceiversForType},
* type -> (packageName -> number of active receivers)
* type == null is used to get notifications about all account types
*/
@@ -237,8 +239,10 @@
UserAccounts(Context context, int userId, File preNDbFile, File deDbFile) {
this.userId = userId;
- synchronized (cacheLock) {
- accountsDb = AccountsDb.create(context, userId, preNDbFile, deDbFile);
+ synchronized (dbLock) {
+ synchronized (cacheLock) {
+ accountsDb = AccountsDb.create(context, userId, preNDbFile, deDbFile);
+ }
}
}
}
@@ -499,12 +503,14 @@
Map<Account, Integer> result = new LinkedHashMap<>();
for (String accountType : accountTypes) {
- synchronized (accounts.cacheLock) {
- final Account[] accountsOfType = accounts.accountCache.get(accountType);
- if (accountsOfType != null) {
- for (Account account : accountsOfType) {
- result.put(account,
- resolveAccountVisibility(account, packageName, accounts));
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ final Account[] accountsOfType = accounts.accountCache.get(accountType);
+ if (accountsOfType != null) {
+ for (Account account : accountsOfType) {
+ result.put(account,
+ resolveAccountVisibility(account, packageName, accounts));
+ }
}
}
}
@@ -524,25 +530,31 @@
String.format("uid %s cannot get secrets for account %s", callingUid, account);
throw new SecurityException(msg);
}
- return getPackagesAndVisibilityForAccount(account, accounts);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ return getPackagesAndVisibilityForAccountLocked(account, accounts);
+ }
+ }
}
/**
- * Returns all package names and visibility values, which were set for given account.
+ * Returns Map with all package names and visibility values for given account.
+ * The method and returned map must be guarded by accounts.cacheLock
*
* @param account Account to get visibility values.
* @param accounts UserAccount that currently hosts the account and application
*
- * @return Map from package names to visibility.
+ * @return Map with cache for package names to visibility.
*/
- private Map<String, Integer> getPackagesAndVisibilityForAccount(Account account,
+ private @NonNull Map<String, Integer> getPackagesAndVisibilityForAccountLocked(Account account,
UserAccounts accounts) {
- final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
- try {
- return accounts.accountsDb.findAllVisibilityValuesForAccount(account);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
+ Map<String, Integer> accountVisibility = accounts.visibilityCache.get(account);
+ if (accountVisibility == null) {
+ Log.d(TAG, "Visibility was not initialized");
+ accountVisibility = new HashMap<>();
+ accounts.visibilityCache.put(account, accountVisibility);
}
+ return accountVisibility;
}
@Override
@@ -572,14 +584,15 @@
* @return Visibility value, AccountManager.VISIBILITY_UNDEFINED if no value was stored.
*
*/
- private int getAccountVisibility(Account account, String packageName, UserAccounts accounts) {
- final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
- try {
- Integer visibility =
- accounts.accountsDb.findAccountVisibility(account, packageName);
- return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
+ private int getAccountVisibilityFromCache(Account account, String packageName,
+ UserAccounts accounts) {
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ Map<String, Integer> accountVisibility =
+ getPackagesAndVisibilityForAccountLocked(account, accounts);
+ Integer visibility = accountVisibility.get(packageName);
+ return visibility != null ? visibility : AccountManager.VISIBILITY_UNDEFINED;
+ }
}
}
@@ -595,9 +608,7 @@
*/
private Integer resolveAccountVisibility(Account account, @NonNull String packageName,
UserAccounts accounts) {
-
Preconditions.checkNotNull(packageName, "packageName cannot be null");
-
int uid = -1;
try {
long identityToken = clearCallingIdentity();
@@ -630,7 +641,7 @@
}
// Return stored value if it was set.
- int visibility = getAccountVisibility(account, packageName, accounts);
+ int visibility = getAccountVisibilityFromCache(account, packageName, accounts);
if (AccountManager.VISIBILITY_UNDEFINED != visibility) {
return visibility;
@@ -652,13 +663,13 @@
|| canReadContacts || isPrivileged) {
// Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
// match.
- visibility = getAccountVisibility(account,
+ visibility = getAccountVisibilityFromCache(account,
AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE, accounts);
if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
}
} else {
- visibility = getAccountVisibility(account,
+ visibility = getAccountVisibilityFromCache(account,
AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, accounts);
if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
@@ -727,55 +738,71 @@
*/
private boolean setAccountVisibility(Account account, String packageName, int newVisibility,
boolean notify, UserAccounts accounts) {
- synchronized (accounts.cacheLock) {
- Map<String, Integer> packagesToVisibility;
- if (notify) {
- if (isSpecialPackageKey(packageName)) {
- packagesToVisibility =
- getRequestingPackages(account, accounts);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ Map<String, Integer> packagesToVisibility;
+ if (notify) {
+ if (isSpecialPackageKey(packageName)) {
+ packagesToVisibility =
+ getRequestingPackages(account, accounts);
+ } else {
+ if (!packageExistsForUser(packageName, accounts.userId)) {
+ return false; // package is not installed.
+ }
+ packagesToVisibility = new HashMap<>();
+ packagesToVisibility.put(packageName,
+ resolveAccountVisibility(account, packageName, accounts));
+ }
} else {
- if (!packageExistsForUser(packageName, accounts.userId)) {
- return false; // package is not installed.
+ // Notifications will not be send.
+ if (!isSpecialPackageKey(packageName) &&
+ !packageExistsForUser(packageName, accounts.userId)) {
+ // package is not installed and not meta value.
+ return false;
}
packagesToVisibility = new HashMap<>();
- packagesToVisibility.put(packageName,
- resolveAccountVisibility(account, packageName, accounts));
}
- } else {
- // Notifications will not be send.
- if (!isSpecialPackageKey(packageName) &&
- !packageExistsForUser(packageName, accounts.userId)) {
- // package is not installed and not meta value.
+
+ if (!updateAccountVisibilityLocked(account, packageName, newVisibility, accounts)) {
return false;
}
- packagesToVisibility = new HashMap<>();
- }
- final long accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId < 0) {
+ if (notify) {
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility
+ .entrySet()) {
+ if (packageToVisibility.getValue()
+ != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
+ }
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
+ return true;
+ }
+ }
+ }
+
+ // Update account visibility in cache and database.
+ private boolean updateAccountVisibilityLocked(Account account, String packageName,
+ int newVisibility, UserAccounts accounts) {
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId < 0) {
+ return false;
+ }
+
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ try {
+ if (!accounts.accountsDb.setAccountVisibility(accountId, packageName,
+ newVisibility)) {
return false;
}
-
- final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
- try {
- if (!accounts.accountsDb.setAccountVisibility(accountId, packageName,
- newVisibility)) {
- return false;
- }
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- }
-
- if (notify) {
- for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
- if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
- notifyPackage(packageToVisibility.getKey(), accounts);
- }
- }
- sendAccountsChangedBroadcast(accounts.userId);
- }
- return true;
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
+ Map<String, Integer> accountVisibility =
+ getPackagesAndVisibilityForAccountLocked(account, accounts);
+ accountVisibility.put(packageName, newVisibility);
+ return true;
}
@Override
@@ -956,23 +983,24 @@
mAuthenticatorCache, accounts.userId);
boolean userUnlocked = isLocalUnlockedUser(accounts.userId);
- synchronized (accounts.cacheLock) {
- boolean accountDeleted = false;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ boolean accountDeleted = false;
- // Get a map of stored authenticator types to UID
- final AccountsDb accountsDb = accounts.accountsDb;
- Map<String, Integer> metaAuthUid = accountsDb.findMetaAuthUid();
- // Create a list of authenticator type whose previous uid no longer exists
- HashSet<String> obsoleteAuthType = Sets.newHashSet();
- SparseBooleanArray knownUids = null;
- for (Entry<String, Integer> authToUidEntry : metaAuthUid.entrySet()) {
- String type = authToUidEntry.getKey();
- int uid = authToUidEntry.getValue();
- Integer knownUid = knownAuth.get(type);
- if (knownUid != null && uid == knownUid) {
- // Remove it from the knownAuth list if it's unchanged.
- knownAuth.remove(type);
- } else {
+ // Get a map of stored authenticator types to UID
+ final AccountsDb accountsDb = accounts.accountsDb;
+ Map<String, Integer> metaAuthUid = accountsDb.findMetaAuthUid();
+ // Create a list of authenticator type whose previous uid no longer exists
+ HashSet<String> obsoleteAuthType = Sets.newHashSet();
+ SparseBooleanArray knownUids = null;
+ for (Entry<String, Integer> authToUidEntry : metaAuthUid.entrySet()) {
+ String type = authToUidEntry.getKey();
+ int uid = authToUidEntry.getValue();
+ Integer knownUid = knownAuth.get(type);
+ if (knownUid != null && uid == knownUid) {
+ // Remove it from the knownAuth list if it's unchanged.
+ knownAuth.remove(type);
+ } else {
/*
* The authenticator is presently not cached and should only be triggered
* when we think an authenticator has been removed (or is being updated).
@@ -989,87 +1017,95 @@
* uninstalled while the authenticator's package is being updated.
*
*/
- if (knownUids == null) {
- knownUids = getUidsOfInstalledOrUpdatedPackagesAsUser(accounts.userId);
- }
- if (!knownUids.get(uid)) {
- // The authenticator is not presently available to the cache. And the
- // package no longer has a data directory (so we surmise it isn't updating).
- // So purge its data from the account databases.
- obsoleteAuthType.add(type);
- // And delete it from the TABLE_META
- accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+ if (knownUids == null) {
+ knownUids = getUidsOfInstalledOrUpdatedPackagesAsUser(accounts.userId);
+ }
+ if (!knownUids.get(uid)) {
+ // The authenticator is not presently available to the cache. And the
+ // package no longer has a data directory (so we surmise it isn't
+ // updating). So purge its data from the account databases.
+ obsoleteAuthType.add(type);
+ // And delete it from the TABLE_META
+ accountsDb.deleteMetaByAuthTypeAndUid(type, uid);
+ }
}
}
- }
- // Add the newly registered authenticator to TABLE_META. If old authenticators have
- // been re-enabled (after being updated for example), then we just overwrite the old
- // values.
- for (Entry<String, Integer> entry : knownAuth.entrySet()) {
- accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
- }
+ // Add the newly registered authenticator to TABLE_META. If old authenticators have
+ // been re-enabled (after being updated for example), then we just overwrite the old
+ // values.
+ for (Entry<String, Integer> entry : knownAuth.entrySet()) {
+ accountsDb.insertOrReplaceMetaAuthTypeAndUid(entry.getKey(), entry.getValue());
+ }
- final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
- try {
- accounts.accountCache.clear();
- final HashMap<String, ArrayList<String>> accountNamesByType = new LinkedHashMap<>();
- for (Entry<Long, Account> accountEntry : accountsMap.entrySet()) {
- final long accountId = accountEntry.getKey();
- final Account account = accountEntry.getValue();
- if (obsoleteAuthType.contains(account.type)) {
- Slog.w(TAG, "deleting account " + account.name + " because type "
- + account.type + "'s registered authenticator no longer exist.");
- Map<String, Integer> packagesToVisibility =
- getRequestingPackages(account, accounts);
- accountsDb.beginTransaction();
- try {
- accountsDb.deleteDeAccount(accountId);
- // Also delete from CE table if user is unlocked; if user is currently
- // locked the account will be removed later by syncDeCeAccountsLocked
- if (userUnlocked) {
- accountsDb.deleteCeAccount(accountId);
+ final Map<Long, Account> accountsMap = accountsDb.findAllDeAccounts();
+ try {
+ accounts.accountCache.clear();
+ final HashMap<String, ArrayList<String>> accountNamesByType
+ = new LinkedHashMap<>();
+ for (Entry<Long, Account> accountEntry : accountsMap.entrySet()) {
+ final long accountId = accountEntry.getKey();
+ final Account account = accountEntry.getValue();
+ if (obsoleteAuthType.contains(account.type)) {
+ Slog.w(TAG, "deleting account " + account.name + " because type "
+ + account.type
+ + "'s registered authenticator no longer exist.");
+ Map<String, Integer> packagesToVisibility =
+ getRequestingPackages(account, accounts);
+ accountsDb.beginTransaction();
+ try {
+ accountsDb.deleteDeAccount(accountId);
+ // Also delete from CE table if user is unlocked; if user is
+ // currently locked the account will be removed later by
+ // syncDeCeAccountsLocked
+ if (userUnlocked) {
+ accountsDb.deleteCeAccount(accountId);
+ }
+ accountsDb.setTransactionSuccessful();
+ } finally {
+ accountsDb.endTransaction();
}
- accountsDb.setTransactionSuccessful();
- } finally {
- accountsDb.endTransaction();
- }
- accountDeleted = true;
+ accountDeleted = true;
- logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
- AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+ logRecord(AccountsDb.DEBUG_ACTION_AUTHENTICATOR_REMOVE,
+ AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
- accounts.userDataCache.remove(account);
- accounts.authTokenCache.remove(account);
- accounts.accountTokenCaches.remove(account);
+ accounts.userDataCache.remove(account);
+ accounts.authTokenCache.remove(account);
+ accounts.accountTokenCaches.remove(account);
+ accounts.visibilityCache.remove(account);
- for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
- if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
- notifyPackage(packageToVisibility.getKey(), accounts);
+ for (Entry<String, Integer> packageToVisibility :
+ packagesToVisibility.entrySet()) {
+ if (packageToVisibility.getValue()
+ != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
}
+ } else {
+ ArrayList<String> accountNames = accountNamesByType.get(account.type);
+ if (accountNames == null) {
+ accountNames = new ArrayList<>();
+ accountNamesByType.put(account.type, accountNames);
+ }
+ accountNames.add(account.name);
}
- } else {
- ArrayList<String> accountNames = accountNamesByType.get(account.type);
- if (accountNames == null) {
- accountNames = new ArrayList<>();
- accountNamesByType.put(account.type, accountNames);
+ }
+ for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) {
+ final String accountType = cur.getKey();
+ final ArrayList<String> accountNames = cur.getValue();
+ final Account[] accountsForType = new Account[accountNames.size()];
+ for (int i = 0; i < accountsForType.length; i++) {
+ accountsForType[i] = new Account(accountNames.get(i), accountType,
+ UUID.randomUUID().toString());
}
- accountNames.add(account.name);
+ accounts.accountCache.put(accountType, accountsForType);
}
- }
- for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) {
- final String accountType = cur.getKey();
- final ArrayList<String> accountNames = cur.getValue();
- final Account[] accountsForType = new Account[accountNames.size()];
- for (int i = 0; i < accountsForType.length; i++) {
- accountsForType[i] = new Account(accountNames.get(i), accountType,
- UUID.randomUUID().toString());
+ accounts.visibilityCache.putAll(accountsDb.findAllVisibilityValues());
+ } finally {
+ if (accountDeleted) {
+ sendAccountsChangedBroadcast(accounts.userId);
}
- accounts.accountCache.put(accountType, accountsForType);
- }
- } finally {
- if (accountDeleted) {
- sendAccountsChangedBroadcast(accounts.userId);
}
}
}
@@ -1129,9 +1165,11 @@
// open CE database if necessary
if (!accounts.accountsDb.isCeDatabaseAttached() && mLocalUnlockedUsers.get(userId)) {
Log.i(TAG, "User " + userId + " is unlocked - opening CE database");
- synchronized (accounts.cacheLock) {
- File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId));
- accounts.accountsDb.attachCeDatabase(ceDatabaseFile);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ File ceDatabaseFile = new File(mInjector.getCeDatabaseName(userId));
+ accounts.accountsDb.attachCeDatabase(ceDatabaseFile);
+ }
}
syncDeCeAccountsLocked(accounts);
}
@@ -1166,34 +1204,50 @@
}
private void purgeOldGrants(UserAccounts accounts) {
- synchronized (accounts.cacheLock) {
- List<Integer> uids = accounts.accountsDb.findAllUidGrants();
- for (int uid : uids) {
- final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
- if (packageExists) {
- continue;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ List<Integer> uids = accounts.accountsDb.findAllUidGrants();
+ for (int uid : uids) {
+ final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
+ if (packageExists) {
+ continue;
+ }
+ Log.d(TAG, "deleting grants for UID " + uid
+ + " because its package is no longer installed");
+ accounts.accountsDb.deleteGrantsByUid(uid);
}
- Log.d(TAG, "deleting grants for UID " + uid
- + " because its package is no longer installed");
- accounts.accountsDb.deleteGrantsByUid(uid);
}
}
}
private void removeVisibilityValuesForPackage(String packageName) {
+ if (isSpecialPackageKey(packageName)) {
+ return;
+ }
synchronized (mUsers) {
- for (int i = 0; i < mUsers.size(); i++) {
- UserAccounts accounts = mUsers.valueAt(i);
- try {
- int uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
- } catch (NameNotFoundException e) {
- // package does not exist - remove visibility values
- accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+ int numberOfUsers = mUsers.size();
+ for (int i = 0; i < numberOfUsers; i++) {
+ UserAccounts accounts = mUsers.valueAt(i);
+ try {
+ mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
+ } catch (NameNotFoundException e) {
+ // package does not exist - remove visibility values
+ accounts.accountsDb.deleteAccountVisibilityForPackage(packageName);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ for (Account account : accounts.visibilityCache.keySet()) {
+ Map<String, Integer> accountVisibility =
+ getPackagesAndVisibilityForAccountLocked(account, accounts);
+ accountVisibility.remove(packageName);
+ }
+ }
+ }
}
}
}
}
+
private void onCleanupUser(int userId) {
Log.i(TAG, "onCleanupUser " + userId);
UserAccounts accounts;
@@ -1203,8 +1257,10 @@
mLocalUnlockedUsers.delete(userId);
}
if (accounts != null) {
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.close();
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.close();
+ }
}
}
}
@@ -1284,8 +1340,11 @@
return null;
}
- synchronized (accounts.cacheLock) {
- return accounts.accountsDb.findAccountPasswordByNameAndType(account.name, account.type);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ return accounts.accountsDb
+ .findAccountPasswordByNameAndType(account.name, account.type);
+ }
}
}
@@ -1311,15 +1370,17 @@
if (account == null) {
return null;
}
- synchronized (accounts.cacheLock) {
- AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
- if (previousNameRef == null) {
- String previousName = accounts.accountsDb.findDeAccountPreviousName(account);
- previousNameRef = new AtomicReference<>(previousName);
- accounts.previousNameCache.put(account, previousNameRef);
- return previousName;
- } else {
- return previousNameRef.get();
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ AtomicReference<String> previousNameRef = accounts.previousNameCache.get(account);
+ if (previousNameRef == null) {
+ String previousName = accounts.accountsDb.findDeAccountPreviousName(account);
+ previousNameRef = new AtomicReference<>(previousName);
+ accounts.previousNameCache.put(account, previousNameRef);
+ return previousName;
+ } else {
+ return previousNameRef.get();
+ }
}
}
}
@@ -1349,11 +1410,13 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- if (!accountExistsCacheLocked(accounts, account)) {
- return null;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return null;
+ }
+ return readUserDataInternalLocked(accounts, account, key);
}
- return readUserDataInternalLocked(accounts, account, key);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1512,8 +1575,10 @@
private boolean updateLastAuthenticatedTime(Account account) {
final UserAccounts accounts = getUserAccountsForCaller();
- synchronized (accounts.cacheLock) {
- return accounts.accountsDb.updateAccountLastAuthenticatedTime(account);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ return accounts.accountsDb.updateAccountLastAuthenticatedTime(account);
+ }
}
}
@@ -1536,13 +1601,15 @@
public void run() throws RemoteException {
// Confirm that the owner's account still exists before this step.
UserAccounts owner = getUserAccounts(parentUserId);
- synchronized (owner.cacheLock) {
- for (Account acc : getAccounts(parentUserId,
- mContext.getOpPackageName())) {
- if (acc.equals(account)) {
- mAuthenticator.addAccountFromCredentials(
- this, account, accountCredentials);
- break;
+ synchronized (owner.dbLock) {
+ synchronized (owner.cacheLock) {
+ for (Account acc : getAccounts(parentUserId,
+ mContext.getOpPackageName())) {
+ if (acc.equals(account)) {
+ mAuthenticator.addAccountFromCredentials(
+ this, account, accountCredentials);
+ break;
+ }
}
}
}
@@ -1583,51 +1650,55 @@
+ " is locked. callingUid=" + callingUid);
return false;
}
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- try {
- if (accounts.accountsDb.findCeAccountId(account) >= 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since the account already exists");
- return false;
- }
- long accountId = accounts.accountsDb.insertCeAccount(account, password);
- if (accountId < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping the DB insert failed");
- return false;
- }
- // Insert into DE table
- if (accounts.accountsDb.insertDeAccount(account, accountId) < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping the DB insert failed");
- return false;
- }
- if (extras != null) {
- for (String key : extras.keySet()) {
- final String value = extras.getString(key);
- if (accounts.accountsDb.insertExtra(accountId, key, value) < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since insertExtra failed for key " + key);
- return false;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ try {
+ if (accounts.accountsDb.findCeAccountId(account) >= 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping since the account already exists");
+ return false;
+ }
+ long accountId = accounts.accountsDb.insertCeAccount(account, password);
+ if (accountId < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping the DB insert failed");
+ return false;
+ }
+ // Insert into DE table
+ if (accounts.accountsDb.insertDeAccount(account, accountId) < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping the DB insert failed");
+ return false;
+ }
+ if (extras != null) {
+ for (String key : extras.keySet()) {
+ final String value = extras.getString(key);
+ if (accounts.accountsDb.insertExtra(accountId, key, value) < 0) {
+ Log.w(TAG, "insertAccountIntoDatabase: " + account
+ + ", skipping since insertExtra failed for key " + key);
+ return false;
+ }
}
}
- }
- if (packageToVisibility != null) {
- for (Entry<String, Integer> entry : packageToVisibility.entrySet()) {
- setAccountVisibility(account, entry.getKey() /* package */,
- entry.getValue() /* visibility */, false /* notify */, accounts);
+ if (packageToVisibility != null) {
+ for (Entry<String, Integer> entry : packageToVisibility.entrySet()) {
+ setAccountVisibility(account, entry.getKey() /* package */,
+ entry.getValue() /* visibility */, false /* notify */,
+ accounts);
+ }
}
+ accounts.accountsDb.setTransactionSuccessful();
+
+ logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS,
+ accountId,
+ accounts, callingUid);
+
+ insertAccountIntoCacheLocked(accounts, account);
+ } finally {
+ accounts.accountsDb.endTransaction();
}
- accounts.accountsDb.setTransactionSuccessful();
-
- logRecord(AccountsDb.DEBUG_ACTION_ACCOUNT_ADD, AccountsDb.TABLE_ACCOUNTS, accountId,
- accounts, callingUid);
-
- insertAccountIntoCacheLocked(accounts, account);
- } finally {
- accounts.accountsDb.endTransaction();
}
}
if (getUserManager().getUserInfo(accounts.userId).canHaveProfile()) {
@@ -1812,72 +1883,76 @@
}
}
}
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- Account renamedAccount = new Account(newName, accountToRename.type);
- if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
- Log.e(TAG, "renameAccount failed - account with new name already exists");
- return null;
- }
- try {
- final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
- if (accountId >= 0) {
- accounts.accountsDb.renameCeAccount(accountId, newName);
- if (accounts.accountsDb.renameDeAccount(
- accountId, newName, accountToRename.name)) {
- accounts.accountsDb.setTransactionSuccessful();
- } else {
- Log.e(TAG, "renameAccount failed");
- return null;
- }
- } else {
- Log.e(TAG, "renameAccount failed - old account does not exist");
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ Account renamedAccount = new Account(newName, accountToRename.type);
+ if ((accounts.accountsDb.findCeAccountId(renamedAccount) >= 0)) {
+ Log.e(TAG, "renameAccount failed - account with new name already exists");
return null;
}
- } finally {
- accounts.accountsDb.endTransaction();
- }
+ try {
+ final long accountId = accounts.accountsDb.findDeAccountId(accountToRename);
+ if (accountId >= 0) {
+ accounts.accountsDb.renameCeAccount(accountId, newName);
+ if (accounts.accountsDb.renameDeAccount(
+ accountId, newName, accountToRename.name)) {
+ accounts.accountsDb.setTransactionSuccessful();
+ } else {
+ Log.e(TAG, "renameAccount failed");
+ return null;
+ }
+ } else {
+ Log.e(TAG, "renameAccount failed - old account does not exist");
+ return null;
+ }
+ } finally {
+ accounts.accountsDb.endTransaction();
+ }
/*
* Database transaction was successful. Clean up cached
* data associated with the account in the user profile.
*/
- renamedAccount = insertAccountIntoCacheLocked(accounts, renamedAccount);
+ renamedAccount = insertAccountIntoCacheLocked(accounts, renamedAccount);
/*
* Extract the data and token caches before removing the
* old account to preserve the user data associated with
* the account.
*/
- Map<String, String> tmpData = accounts.userDataCache.get(accountToRename);
- Map<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
- removeAccountFromCacheLocked(accounts, accountToRename);
+ Map<String, String> tmpData = accounts.userDataCache.get(accountToRename);
+ Map<String, String> tmpTokens = accounts.authTokenCache.get(accountToRename);
+ Map<String, Integer> tmpVisibility = accounts.visibilityCache.get(accountToRename);
+ removeAccountFromCacheLocked(accounts, accountToRename);
/*
* Update the cached data associated with the renamed
* account.
*/
- accounts.userDataCache.put(renamedAccount, tmpData);
- accounts.authTokenCache.put(renamedAccount, tmpTokens);
- accounts.previousNameCache.put(
- renamedAccount,
- new AtomicReference<>(accountToRename.name));
- resultAccount = renamedAccount;
+ accounts.userDataCache.put(renamedAccount, tmpData);
+ accounts.authTokenCache.put(renamedAccount, tmpTokens);
+ accounts.visibilityCache.put(renamedAccount, tmpVisibility);
+ accounts.previousNameCache.put(
+ renamedAccount,
+ new AtomicReference<>(accountToRename.name));
+ resultAccount = renamedAccount;
- int parentUserId = accounts.userId;
- if (canHaveProfile(parentUserId)) {
+ int parentUserId = accounts.userId;
+ if (canHaveProfile(parentUserId)) {
/*
* Owner or system user account was renamed, rename the account for
* those users with which the account was shared.
*/
- List<UserInfo> users = getUserManager().getUsers(true);
- for (UserInfo user : users) {
- if (user.isRestricted()
- && (user.restrictedProfileParentId == parentUserId)) {
- renameSharedAccountAsUser(accountToRename, newName, user.id);
+ List<UserInfo> users = getUserManager().getUsers(true);
+ for (UserInfo user : users) {
+ if (user.isRestricted()
+ && (user.restrictedProfileParentId == parentUserId)) {
+ renameSharedAccountAsUser(accountToRename, newName, user.id);
+ }
}
}
- }
- sendNotificationAccountUpdated(resultAccount, accounts);
- sendAccountsChangedBroadcast(accounts.userId);
+ sendNotificationAccountUpdated(resultAccount, accounts);
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
}
return resultAccount;
}
@@ -2075,42 +2150,47 @@
Slog.i(TAG, "Removing account " + account + " while user "+ accounts.userId
+ " is still locked. CE data will be removed later");
}
- synchronized (accounts.cacheLock) {
- Map<String, Integer> packagesToVisibility = getRequestingPackages(account, accounts);
- accounts.accountsDb.beginTransaction();
- // Set to a dummy value, this will only be used if the database
- // transaction succeeds.
- long accountId = -1;
- try {
- accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId >= 0) {
- isChanged = accounts.accountsDb.deleteDeAccount(accountId);
- }
- // always delete from CE table if CE storage is available
- // DE account could be removed while CE was locked
- if (userUnlocked) {
- long ceAccountId = accounts.accountsDb.findCeAccountId(account);
- if (ceAccountId >= 0) {
- accounts.accountsDb.deleteCeAccount(ceAccountId);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ Map<String, Integer> packagesToVisibility = getRequestingPackages(account,
+ accounts);
+ accounts.accountsDb.beginTransaction();
+ // Set to a dummy value, this will only be used if the database
+ // transaction succeeds.
+ long accountId = -1;
+ try {
+ accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId >= 0) {
+ isChanged = accounts.accountsDb.deleteDeAccount(accountId);
}
- }
- accounts.accountsDb.setTransactionSuccessful();
- } finally {
- accounts.accountsDb.endTransaction();
- }
- if (isChanged) {
- removeAccountFromCacheLocked(accounts, account);
- for (Entry<String, Integer> packageToVisibility : packagesToVisibility.entrySet()) {
- if (packageToVisibility.getValue() != AccountManager.VISIBILITY_NOT_VISIBLE) {
- notifyPackage(packageToVisibility.getKey(), accounts);
+ // always delete from CE table if CE storage is available
+ // DE account could be removed while CE was locked
+ if (userUnlocked) {
+ long ceAccountId = accounts.accountsDb.findCeAccountId(account);
+ if (ceAccountId >= 0) {
+ accounts.accountsDb.deleteCeAccount(ceAccountId);
+ }
}
+ accounts.accountsDb.setTransactionSuccessful();
+ } finally {
+ accounts.accountsDb.endTransaction();
}
+ if (isChanged) {
+ removeAccountFromCacheLocked(accounts, account);
+ for (Entry<String, Integer> packageToVisibility : packagesToVisibility
+ .entrySet()) {
+ if (packageToVisibility.getValue()
+ != AccountManager.VISIBILITY_NOT_VISIBLE) {
+ notifyPackage(packageToVisibility.getKey(), accounts);
+ }
+ }
- // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
- sendAccountsChangedBroadcast(accounts.userId);
- String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
- : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
- logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+ // Only broadcast LOGIN_ACCOUNTS_CHANGED if a change occurred.
+ sendAccountsChangedBroadcast(accounts.userId);
+ String action = userUnlocked ? AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE
+ : AccountsDb.DEBUG_ACTION_ACCOUNT_REMOVE_DE;
+ logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts);
+ }
}
}
long id = Binder.clearCallingIdentity();
@@ -2160,14 +2240,16 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- try {
- invalidateAuthTokenLocked(accounts, accountType, authToken);
- invalidateCustomTokenLocked(accounts, accountType, authToken);
- accounts.accountsDb.setTransactionSuccessful();
- } finally {
- accounts.accountsDb.endTransaction();
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ try {
+ invalidateAuthTokenLocked(accounts, accountType, authToken);
+ invalidateCustomTokenLocked(accounts, accountType, authToken);
+ accounts.accountsDb.setTransactionSuccessful();
+ } finally {
+ accounts.accountsDb.endTransaction();
+ }
}
}
} finally {
@@ -2223,9 +2305,11 @@
}
cancelNotification(getSigninRequiredNotificationId(accounts, account),
UserHandle.of(accounts.userId));
- synchronized (accounts.cacheLock) {
- accounts.accountTokenCaches.put(
- account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountTokenCaches.put(
+ account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
+ }
}
}
@@ -2236,22 +2320,24 @@
}
cancelNotification(getSigninRequiredNotificationId(accounts, account),
UserHandle.of(accounts.userId));
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- try {
- long accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId < 0) {
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ try {
+ long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId < 0) {
+ return false;
+ }
+ accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
+ if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
+ accounts.accountsDb.setTransactionSuccessful();
+ writeAuthTokenIntoCacheLocked(accounts, account, type, authToken);
+ return true;
+ }
return false;
+ } finally {
+ accounts.accountsDb.endTransaction();
}
- accounts.accountsDb.deleteAuthtokensByAccountIdAndType(accountId, type);
- if (accounts.accountsDb.insertAuthToken(accountId, type, authToken) >= 0) {
- accounts.accountsDb.setTransactionSuccessful();
- writeAuthTokenIntoCacheLocked(accounts, account, type, authToken);
- return true;
- }
- return false;
- } finally {
- accounts.accountsDb.endTransaction();
}
}
}
@@ -2349,31 +2435,35 @@
return;
}
boolean isChanged = false;
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- try {
- final long accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId >= 0) {
- accounts.accountsDb.updateCeAccountPassword(accountId, password);
- accounts.accountsDb.deleteAuthTokensByAccountId(accountId);
- accounts.authTokenCache.remove(account);
- accounts.accountTokenCaches.remove(account);
- accounts.accountsDb.setTransactionSuccessful();
- // If there is an account whose password will be updated and the database
- // transactions succeed, then we say that a change has occured. Even if the
- // new password is the same as the old and there were no authtokens to delete.
- isChanged = true;
- String action = (password == null || password.length() == 0) ?
- AccountsDb.DEBUG_ACTION_CLEAR_PASSWORD
- : AccountsDb.DEBUG_ACTION_SET_PASSWORD;
- logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts, callingUid);
- }
- } finally {
- accounts.accountsDb.endTransaction();
- if (isChanged) {
- // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
- sendNotificationAccountUpdated(account, accounts);
- sendAccountsChangedBroadcast(accounts.userId);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ try {
+ final long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId >= 0) {
+ accounts.accountsDb.updateCeAccountPassword(accountId, password);
+ accounts.accountsDb.deleteAuthTokensByAccountId(accountId);
+ accounts.authTokenCache.remove(account);
+ accounts.accountTokenCaches.remove(account);
+ accounts.accountsDb.setTransactionSuccessful();
+ // If there is an account whose password will be updated and the database
+ // transactions succeed, then we say that a change has occured. Even if the
+ // new password is the same as the old and there were no authtokens to
+ // delete.
+ isChanged = true;
+ String action = (password == null || password.length() == 0) ?
+ AccountsDb.DEBUG_ACTION_CLEAR_PASSWORD
+ : AccountsDb.DEBUG_ACTION_SET_PASSWORD;
+ logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts,
+ callingUid);
+ }
+ } finally {
+ accounts.accountsDb.endTransaction();
+ if (isChanged) {
+ // Send LOGIN_ACCOUNTS_CHANGED only if the something changed.
+ sendNotificationAccountUpdated(account, accounts);
+ sendAccountsChangedBroadcast(accounts.userId);
+ }
}
}
}
@@ -2427,11 +2517,13 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- if (!accountExistsCacheLocked(accounts, account)) {
- return;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ if (!accountExistsCacheLocked(accounts, account)) {
+ return;
+ }
+ setUserdataInternalLocked(accounts, account, key, value);
}
- setUserdataInternalLocked(accounts, account, key, value);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -3855,9 +3947,12 @@
@Override
public void run() throws RemoteException {
- synchronized (mAccounts.cacheLock) {
- mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
- mPackageName, false /* include managed not visible*/);
+ synchronized (mAccounts.dbLock) {
+ synchronized (mAccounts.cacheLock) {
+ mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType,
+ mCallingUid,
+ mPackageName, false /* include managed not visible*/);
+ }
}
// check whether each account matches the requested features
mAccountsWithFeatures = new ArrayList<>(mAccountsOfType.length);
@@ -4002,15 +4097,17 @@
for (int userId : userIds) {
UserAccounts userAccounts = getUserAccounts(userId);
if (userAccounts == null) continue;
- synchronized (userAccounts.cacheLock) {
- Account[] accounts = getAccountsFromCacheLocked(
- userAccounts,
- null /* type */,
- Binder.getCallingUid(),
- null /* packageName */,
- false /* include managed not visible*/);
- for (int a = 0; a < accounts.length; a++) {
- runningAccounts.add(new AccountAndUser(accounts[a], userId));
+ synchronized (userAccounts.dbLock) {
+ synchronized (userAccounts.cacheLock) {
+ Account[] accounts = getAccountsFromCacheLocked(
+ userAccounts,
+ null /* type */,
+ Binder.getCallingUid(),
+ null /* packageName */,
+ false /* include managed not visible*/);
+ for (int a = 0; a < accounts.length; a++) {
+ runningAccounts.add(new AccountAndUser(accounts[a], userId));
+ }
}
}
}
@@ -4097,21 +4194,23 @@
String callingPackage,
List<String> visibleAccountTypes,
boolean includeUserManagedNotVisible) {
- synchronized (userAccounts.cacheLock) {
- ArrayList<Account> visibleAccounts = new ArrayList<>();
- for (String visibleType : visibleAccountTypes) {
- Account[] accountsForType = getAccountsFromCacheLocked(
- userAccounts, visibleType, callingUid, callingPackage,
- includeUserManagedNotVisible);
- if (accountsForType != null) {
- visibleAccounts.addAll(Arrays.asList(accountsForType));
+ synchronized (userAccounts.dbLock) {
+ synchronized (userAccounts.cacheLock) {
+ ArrayList<Account> visibleAccounts = new ArrayList<>();
+ for (String visibleType : visibleAccountTypes) {
+ Account[] accountsForType = getAccountsFromCacheLocked(
+ userAccounts, visibleType, callingUid, callingPackage,
+ includeUserManagedNotVisible);
+ if (accountsForType != null) {
+ visibleAccounts.addAll(Arrays.asList(accountsForType));
+ }
}
+ Account[] result = new Account[visibleAccounts.size()];
+ for (int i = 0; i < visibleAccounts.size(); i++) {
+ result[i] = visibleAccounts.get(i);
+ }
+ return result;
}
- Account[] result = new Account[visibleAccounts.size()];
- for (int i = 0; i < visibleAccounts.size(); i++) {
- result[i] = visibleAccounts.get(i);
- }
- return result;
}
}
@@ -4262,9 +4361,11 @@
UserAccounts userAccounts = getUserAccounts(userId);
if (features == null || features.length == 0) {
Account[] accounts;
- synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(
- userAccounts, type, callingUid, opPackageName, false);
+ synchronized (userAccounts.dbLock) {
+ synchronized (userAccounts.cacheLock) {
+ accounts = getAccountsFromCacheLocked(
+ userAccounts, type, callingUid, opPackageName, false);
+ }
}
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
@@ -4821,32 +4922,35 @@
private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
String[] args, boolean isCheckinRequest) {
- synchronized (userAccounts.cacheLock) {
- if (isCheckinRequest) {
- // This is a checkin request. *Only* upload the account types and the count of each.
- userAccounts.accountsDb.dumpDeAccountsTable(fout);
- } else {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
- Process.SYSTEM_UID, null /* packageName */, false);
- fout.println("Accounts: " + accounts.length);
- for (Account account : accounts) {
- fout.println(" " + account);
- }
-
- // Add debug information.
- fout.println();
- userAccounts.accountsDb.dumpDebugTable(fout);
- fout.println();
- synchronized (mSessions) {
- final long now = SystemClock.elapsedRealtime();
- fout.println("Active Sessions: " + mSessions.size());
- for (Session session : mSessions.values()) {
- fout.println(" " + session.toDebugString(now));
+ synchronized (userAccounts.dbLock) {
+ synchronized (userAccounts.cacheLock) {
+ if (isCheckinRequest) {
+ // This is a checkin request. *Only* upload the account types and the count of
+ // each.
+ userAccounts.accountsDb.dumpDeAccountsTable(fout);
+ } else {
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+ Process.SYSTEM_UID, null /* packageName */, false);
+ fout.println("Accounts: " + accounts.length);
+ for (Account account : accounts) {
+ fout.println(" " + account);
}
- }
- fout.println();
- mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+ // Add debug information.
+ fout.println();
+ userAccounts.accountsDb.dumpDebugTable(fout);
+ fout.println();
+ synchronized (mSessions) {
+ final long now = SystemClock.elapsedRealtime();
+ fout.println("Active Sessions: " + mSessions.size());
+ for (Session session : mSessions.values()) {
+ fout.println(" " + session.toDebugString(now));
+ }
+ }
+
+ fout.println();
+ mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+ }
}
}
}
@@ -5160,26 +5264,28 @@
return true;
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callerUid));
- synchronized (accounts.cacheLock) {
- long grantsCount;
- if (authTokenType != null) {
- grantsCount = accounts.accountsDb.findMatchingGrantsCount(callerUid, authTokenType,
- account);
- } else {
- grantsCount = accounts.accountsDb.findMatchingGrantsCountAnyToken(callerUid,
- account);
- }
- final boolean permissionGranted = grantsCount > 0;
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ long grantsCount;
+ if (authTokenType != null) {
+ grantsCount = accounts.accountsDb
+ .findMatchingGrantsCount(callerUid, authTokenType, account);
+ } else {
+ grantsCount = accounts.accountsDb.findMatchingGrantsCountAnyToken(callerUid,
+ account);
+ }
+ final boolean permissionGranted = grantsCount > 0;
- if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
- // TODO: Skip this check when running automated tests. Replace this
- // with a more general solution.
- Log.d(TAG, "no credentials permission for usage of " + account + ", "
- + authTokenType + " by uid " + callerUid
- + " but ignoring since device is in test harness.");
- return true;
+ if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
+ // TODO: Skip this check when running automated tests. Replace this
+ // with a more general solution.
+ Log.d(TAG, "no credentials permission for usage of " + account + ", "
+ + authTokenType + " by uid " + callerUid
+ + " but ignoring since device is in test harness.");
+ return true;
+ }
+ return permissionGranted;
}
- return permissionGranted;
}
}
@@ -5293,15 +5399,18 @@
return;
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- long accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId >= 0) {
- accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- UserHandle.of(accounts.userId));
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId >= 0) {
+ accounts.accountsDb.insertGrant(accountId, authTokenType, uid);
+ }
+ cancelNotification(
+ getCredentialPermissionNotificationId(account, authTokenType, uid),
+ UserHandle.of(accounts.userId));
- cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
+ cancelAccountAccessRequestNotificationIfNeeded(account, uid, true);
+ }
}
// Listeners are a final CopyOnWriteArrayList, hence no lock needed.
@@ -5325,21 +5434,24 @@
return;
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- accounts.accountsDb.beginTransaction();
- try {
- long accountId = accounts.accountsDb.findDeAccountId(account);
- if (accountId >= 0) {
- accounts.accountsDb.deleteGrantsByAccountIdAuthTokenTypeAndUid(
- accountId, authTokenType, uid);
- accounts.accountsDb.setTransactionSuccessful();
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ accounts.accountsDb.beginTransaction();
+ try {
+ long accountId = accounts.accountsDb.findDeAccountId(account);
+ if (accountId >= 0) {
+ accounts.accountsDb.deleteGrantsByAccountIdAuthTokenTypeAndUid(
+ accountId, authTokenType, uid);
+ accounts.accountsDb.setTransactionSuccessful();
+ }
+ } finally {
+ accounts.accountsDb.endTransaction();
}
- } finally {
- accounts.accountsDb.endTransaction();
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
+ cancelNotification(
+ getCredentialPermissionNotificationId(account, authTokenType, uid),
+ UserHandle.of(accounts.userId));
+ }
}
// Listeners are a final CopyOnWriteArrayList, hence no lock needed.
@@ -5369,6 +5481,7 @@
accounts.userDataCache.remove(account);
accounts.authTokenCache.remove(account);
accounts.previousNameCache.remove(account);
+ accounts.visibilityCache.remove(account);
}
/**
@@ -5548,9 +5661,11 @@
String tokenType,
String callingPackage,
byte[] pkgSigDigest) {
- synchronized (accounts.cacheLock) {
- return accounts.accountTokenCaches.get(
- account, tokenType, callingPackage, pkgSigDigest);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ return accounts.accountTokenCaches.get(
+ account, tokenType, callingPackage, pkgSigDigest);
+ }
}
}
@@ -5570,14 +5685,16 @@
protected String readAuthTokenInternal(UserAccounts accounts, Account account,
String authTokenType) {
- synchronized (accounts.cacheLock) {
- Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
- if (authTokensForAccount == null) {
- // need to populate the cache for this account
- authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
- accounts.authTokenCache.put(account, authTokensForAccount);
+ synchronized (accounts.dbLock) {
+ synchronized (accounts.cacheLock) {
+ Map<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+ if (authTokensForAccount == null) {
+ // need to populate the cache for this account
+ authTokensForAccount = accounts.accountsDb.findAuthTokensByAccount(account);
+ accounts.authTokenCache.put(account, authTokensForAccount);
+ }
+ return authTokensForAccount.get(authTokenType);
}
- return authTokensForAccount.get(authTokenType);
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index 22543cb..8ca7ea1 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -909,7 +909,7 @@
}
Integer findAccountVisibility(Account account, String packageName) {
- SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
new String[] {account.name, account.type, packageName}, null, null, null);
@@ -924,7 +924,7 @@
}
Integer findAccountVisibility(long accountId, String packageName) {
- SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
new String[] {String.valueOf(accountId), packageName}, null, null, null);
@@ -972,6 +972,41 @@
return result;
}
+ /**
+ * Returns a map account -> (package -> visibility)
+ */
+ Map <Account, Map<String, Integer>> findAllVisibilityValues() {
+ SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+ Map<Account, Map<String, Integer>> result = new HashMap<>();
+ Cursor cursor = db.rawQuery(
+ "SELECT " + TABLE_VISIBILITY + "." + VISIBILITY_PACKAGE
+ + ", " + TABLE_VISIBILITY + "." + VISIBILITY_VALUE
+ + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE
+ + " FROM " + TABLE_VISIBILITY
+ + " JOIN " + TABLE_ACCOUNTS
+ + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ + " = " + TABLE_VISIBILITY + "." + VISIBILITY_ACCOUNTS_ID, null);
+ try {
+ while (cursor.moveToNext()) {
+ String packageName = cursor.getString(0);
+ Integer visibility = cursor.getInt(1);
+ String accountName = cursor.getString(2);
+ String accountType = cursor.getString(3);
+ Account account = new Account(accountName, accountType);
+ Map <String, Integer> accountVisibility = result.get(account);
+ if (accountVisibility == null) {
+ accountVisibility = new HashMap<>();
+ result.put(account, accountVisibility);
+ }
+ accountVisibility.put(packageName, visibility);
+ }
+ } finally {
+ cursor.close();
+ }
+ return result;
+ }
+
boolean deleteAccountVisibilityForPackage(String packageName) {
SQLiteDatabase db = mDeDatabase.getWritableDatabase();
return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 272fbf8..7a83436 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2743,6 +2743,7 @@
@VisibleForTesting
public ActivityManagerService(Injector injector) {
mInjector = injector;
+ mContext = mInjector.getContext();
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
@@ -23875,6 +23876,10 @@
public static class Injector {
private NetworkManagementInternal mNmi;
+ public Context getContext() {
+ return null;
+ }
+
public AppOpsService getAppOpsService(File file, Handler handler) {
return new AppOpsService(file, handler);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index aaad12c..9b6d13a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -82,6 +82,8 @@
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
final class ActivityManagerShellCommand extends ShellCommand {
public static final String NO_CLASS_ERROR_CODE = "Error type 3";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -118,6 +120,8 @@
private boolean mStreaming; // Streaming the profiling output to a file.
private int mDisplayId;
private int mStackId;
+ private int mTaskId;
+ private boolean mIsTaskOverlay;
final boolean mDumping;
@@ -263,6 +267,8 @@
mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
mStackId = INVALID_STACK_ID;
+ mTaskId = INVALID_TASK_ID;
+ mIsTaskOverlay = false;
return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
@Override
@@ -297,6 +303,10 @@
mDisplayId = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--stack")) {
mStackId = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--task")) {
+ mTaskId = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--task-overlay")) {
+ mIsTaskOverlay = true;
} else {
return false;
}
@@ -380,6 +390,14 @@
options = ActivityOptions.makeBasic();
options.setLaunchStackId(mStackId);
}
+ if (mTaskId != INVALID_TASK_ID) {
+ options = ActivityOptions.makeBasic();
+ options.setLaunchTaskId(mTaskId);
+
+ if (mIsTaskOverlay) {
+ options.setTaskOverlay(true, true /* canResume */);
+ }
+ }
if (mWaitOption) {
result = mInterface.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo,
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 28a4e1a..498de63 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -67,7 +67,9 @@
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -121,7 +123,6 @@
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
import com.android.server.wm.StackWindowController;
import com.android.server.wm.StackWindowListener;
-import com.android.server.wm.TaskStack;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
@@ -1185,13 +1186,13 @@
* @param resuming The activity we are currently trying to resume or null if this is not being
* called as part of resuming the top activity, so we shouldn't try to instigate
* a resume here if not null.
- * @param dontWait True if the caller does not want to wait for the pause to complete. If
- * set to true, we will immediately complete the pause here before returning.
+ * @param pauseImmediately True if the caller does not want to wait for the activity callback to
+ * complete pausing.
* @return Returns true if an activity now is in the PAUSING state, and we are waiting for
* it to tell us when it is done.
*/
final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
- ActivityRecord resuming, boolean dontWait) {
+ ActivityRecord resuming, boolean pauseImmediately) {
if (mPausingActivity != null) {
Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+ " state=" + mPausingActivity.state);
@@ -1213,7 +1214,8 @@
if (mActivityContainer.mParentActivity == null) {
// Top level stack, not a child. Look for child stacks.
- mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping, resuming, dontWait);
+ mStackSupervisor.pauseChildStacks(prev, userLeaving, uiSleeping, resuming,
+ pauseImmediately);
}
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSING: " + prev);
@@ -1243,7 +1245,7 @@
prev.shortComponentName);
mService.updateUsageStats(prev, false);
prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
- userLeaving, prev.configChangeFlags, dontWait);
+ userLeaving, prev.configChangeFlags, pauseImmediately);
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
@@ -1274,7 +1276,7 @@
Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off");
}
- if (dontWait) {
+ if (pauseImmediately) {
// If the caller said they don't want to wait for the pause, then complete
// the pause now.
completePauseLocked(false, resuming);
@@ -3488,11 +3490,19 @@
}
/**
+ * See {@link #finishActivityLocked(ActivityRecord, int, Intent, String, boolean, boolean)}
+ */
+ final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
+ String reason, boolean oomAdj) {
+ return finishActivityLocked(r, resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
+ }
+
+ /**
* @return Returns true if this activity has been removed from the history
* list, or false if it is still in the list and will be removed later.
*/
final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
- String reason, boolean oomAdj) {
+ String reason, boolean oomAdj, boolean pauseImmediately) {
if (r.finishing) {
Slog.w(TAG, "Duplicate finish request for " + r);
return false;
@@ -3529,9 +3539,10 @@
if (mResumedActivity == r) {
if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
"Prepare close transition: finishing " + r);
- if (endTask) {
- mService.mTaskChangeNotificationController.notifyTaskRemovalStarted(task.taskId);
- }
+ if (endTask) {
+ mService.mTaskChangeNotificationController.notifyTaskRemovalStarted(
+ task.taskId);
+ }
mWindowManager.prepareAppTransition(transit, false);
// Tell window manager to prepare for this one to be removed.
@@ -3541,7 +3552,7 @@
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + r);
if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
"finish() => pause with userLeaving=false");
- startPausingLocked(false, false, null, false);
+ startPausingLocked(false, false, null, pauseImmediately);
}
if (endTask) {
@@ -3552,15 +3563,30 @@
// it is done pausing; else we can just directly finish it here.
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r);
if (r.visible) {
- mWindowManager.prepareAppTransition(transit, false);
- r.setVisibility(false);
- mWindowManager.executeAppTransition();
- if (!mStackSupervisor.mWaitingVisibleActivities.contains(r)) {
- mStackSupervisor.mWaitingVisibleActivities.add(r);
+ prepareActivityHideTransitionAnimation(r, transit);
+ }
+
+ final int finishMode = (r.visible || r.nowVisible) ? FINISH_AFTER_VISIBLE
+ : FINISH_AFTER_PAUSE;
+ final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj)
+ == null;
+
+ // The following code is an optimization. When the last non-task overlay activity
+ // is removed from the task, we remove the entire task from the stack. However,
+ // since that is done after the scheduled destroy callback from the activity, that
+ // call to change the visibility of the task overlay activities would be out of
+ // sync with the activitiy visibility being set for this finishing activity above.
+ // In this case, we can set the visibility of all the task overlay activities when
+ // we detect the last one is finishing to keep them in sync.
+ if (task.onlyHasTaskOverlayActivities(true /* excludeFinishing */)) {
+ for (ActivityRecord taskOverlay : task.mActivities) {
+ if (!taskOverlay.mTaskOverlay) {
+ continue;
+ }
+ prepareActivityHideTransitionAnimation(taskOverlay, transit);
}
}
- return finishCurrentActivityLocked(r, (r.visible || r.nowVisible) ?
- FINISH_AFTER_VISIBLE : FINISH_AFTER_PAUSE, oomAdj) == null;
+ return removedActivity;
} else {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r);
}
@@ -3571,6 +3597,15 @@
}
}
+ private void prepareActivityHideTransitionAnimation(ActivityRecord r, int transit) {
+ mWindowManager.prepareAppTransition(transit, false);
+ r.setVisibility(false);
+ mWindowManager.executeAppTransition();
+ if (!mStackSupervisor.mWaitingVisibleActivities.contains(r)) {
+ mStackSupervisor.mWaitingVisibleActivities.add(r);
+ }
+ }
+
static final int FINISH_IMMEDIATELY = 0;
static final int FINISH_AFTER_PAUSE = 1;
static final int FINISH_AFTER_VISIBLE = 2;
@@ -3864,15 +3899,23 @@
r.removeWindowContainer();
final TaskRecord task = r.task;
final boolean lastActivity = task != null ? task.removeActivity(r) : false;
+ // If we are removing the last activity in the task, not including task overlay activities,
+ // then fall through into the block below to remove the entire task itself
+ final boolean onlyHasTaskOverlays = task != null
+ ? task.onlyHasTaskOverlayActivities(false /* excludingFinishing */) : false;
- if (lastActivity) {
- if (DEBUG_STACK) Slog.i(TAG_STACK,
- "removeActivityFromHistoryLocked: last activity removed from " + this);
+ if (lastActivity || onlyHasTaskOverlays) {
+ if (DEBUG_STACK) {
+ Slog.i(TAG_STACK,
+ "removeActivityFromHistoryLocked: last activity removed from " + this
+ + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
+ }
+
if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
task.isOverHomeStack()) {
mStackSupervisor.moveHomeStackTaskToTop(reason);
}
- removeTask(task, reason);
+ removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
}
cleanUpActivityServicesLocked(r);
r.removeUriPermissionsLocked();
@@ -4923,10 +4966,6 @@
return starting;
}
- void removeTask(TaskRecord task, String reason) {
- removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
- }
-
/**
* Removes the input task from this stack.
* @param task to remove.
@@ -4936,6 +4975,10 @@
*/
void removeTask(TaskRecord task, String reason, int mode) {
if (mode == REMOVE_TASK_MODE_DESTROYING) {
+ // When destroying a task, tell the supervisor to remove it so that any activity it has
+ // can be cleaned up correctly
+ mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
+ !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
task.removeWindowContainer();
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8559dca..b623b2f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -254,6 +254,10 @@
// Used to indicate that a task is removed it should also be removed from recents.
static final boolean REMOVE_FROM_RECENTS = true;
+ // Used to indicate that pausing an activity should occur immediately without waiting for
+ // the activity callback indicating that it has completed pausing
+ static final boolean PAUSE_IMMEDIATELY = true;
+
/**
* The modes which affect which tasks are returned when calling
* {@link ActivityStackSupervisor#anyTaskForIdLocked(int)}.
@@ -2536,18 +2540,28 @@
}
/**
+ * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
+ */
+ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
+ return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+ }
+
+ /**
* Removes the task with the specified task id.
*
* @param taskId Identifier of the task to be removed.
* @param killProcess Kill any process associated with the task if possible.
* @param removeFromRecents Whether to also remove the task from recents.
+ * @param pauseImmediately Pauses all task activities immediately without waiting for the
+ * pause-complete callback from the activity.
* @return Returns true if the given task was found and removed.
*/
- boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
+ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+ boolean pauseImmediately) {
final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
INVALID_STACK_ID);
if (tr != null) {
- tr.removeTaskActivitiesLocked();
+ tr.removeTaskActivitiesLocked(pauseImmediately);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 7b1af38..c10f77c 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,7 +870,7 @@
nativeProcs = NATIVE_STACKS_OF_INTEREST;
}
- int[] pids = Process.getPidsForCommands(nativeProcs);
+ int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
ArrayList<Integer> nativePids = null;
if (pids != null) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index bff5f51..ce32f84 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -112,6 +112,7 @@
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
+import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static java.lang.Integer.MAX_VALUE;
@@ -1280,6 +1281,26 @@
return false;
}
+ /**
+ * @return whether or not there are ONLY task overlay activities in the stack.
+ * If {@param excludeFinishing} is set, then ignore finishing activities in the check.
+ * If there are no task overlay activities, this call returns false.
+ */
+ boolean onlyHasTaskOverlayActivities(boolean excludeFinishing) {
+ int count = 0;
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mActivities.get(i);
+ if (excludeFinishing && r.finishing) {
+ continue;
+ }
+ if (!r.mTaskOverlay) {
+ return false;
+ }
+ count++;
+ }
+ return count > 0;
+ }
+
boolean autoRemoveFromRecents() {
// We will automatically remove the task either if it has explicitly asked for
// this, or it is empty and has never contained an activity that got shown to
@@ -1291,7 +1312,7 @@
* Completely remove all activities associated with an existing
* task starting at a specified index.
*/
- final void performClearTaskAtIndexLocked(int activityNdx) {
+ final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
int numActivities = mActivities.size();
for ( ; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
@@ -1304,8 +1325,8 @@
mActivities.remove(activityNdx);
--activityNdx;
--numActivities;
- } else if (mStack.finishActivityLocked(
- r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) {
+ } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
+ "clear-task-index", false, pauseImmediately)) {
--activityNdx;
--numActivities;
}
@@ -1317,7 +1338,7 @@
*/
final void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked(0);
+ performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
mReuseTask = false;
}
@@ -1401,9 +1422,9 @@
return taskThumbnail;
}
- void removeTaskActivitiesLocked() {
+ void removeTaskActivitiesLocked(boolean pauseImmediately) {
// Just remove the entire task.
- performClearTaskAtIndexLocked(0);
+ performClearTaskAtIndexLocked(0, pauseImmediately);
}
String lockTaskAuthToString() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e08ab60..49d1521 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -6598,7 +6598,7 @@
public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
final boolean isPrivileged =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged);
}
@@ -6609,7 +6609,7 @@
public List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
final boolean isPrivileged =
- (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
return mPlaybackMonitor.getActivePlaybackConfigurations(isPrivileged);
}
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
new file mode 100644
index 0000000..c6dc11c
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
@@ -0,0 +1,201 @@
+/*
+ * 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.media;
+
+import android.content.Context;
+import android.media.AudioManager.AudioPlaybackCallback;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Monitors changes in audio playback and notify the newly started audio playback through the
+ * {@link OnAudioPlaybackStartedListener}.
+ */
+class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
+ private static boolean DEBUG = MediaSessionService.DEBUG;
+ private static String TAG = "AudioPlaybackMonitor";
+
+ /**
+ * Called when audio playback is started for a given UID.
+ */
+ interface OnAudioPlaybackStartedListener {
+ void onAudioPlaybackStarted(int uid);
+ }
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+ private final OnAudioPlaybackStartedListener mListener;
+
+ private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
+ private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
+
+ // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+ // The UID whose audio playback becomes active at the last comes first.
+ // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+ private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+ AudioPlaybackMonitor(Context context, IAudioService audioService,
+ OnAudioPlaybackStartedListener listener) {
+ mContext = context;
+ mListener = listener;
+ try {
+ audioService.registerPlaybackCallback(this);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failed to register playback callback", e);
+ }
+ }
+
+ /**
+ * Called when the {@link AudioPlaybackConfiguration} is updated.
+ * <p>If an app starts audio playback, the app's local media session will be the media button
+ * session. If the app has multiple media sessions, the playback active local session will be
+ * picked.
+ *
+ * @param configs List of the current audio playback configuration
+ */
+ @Override
+ public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>();
+ List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
+ synchronized (mLock) {
+ mActiveAudioPlaybackClientUids.clear();
+ for (AudioPlaybackConfiguration config : configs) {
+ // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
+ // (i.e. playback from the SoundPool class which is only for sound effects)
+ // playback.
+ // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
+ // specific audio/video players.
+ if (!config.isActive()
+ || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ continue;
+ }
+ mActiveAudioPlaybackClientUids.add(config.getClientUid());
+
+ newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId());
+ if (!mActiveAudioPlaybackPlayerInterfaceIds.contains(
+ config.getPlayerInterfaceId())) {
+ if (DEBUG) {
+ Log.d(TAG, "Found a new active media playback. " +
+ AudioPlaybackConfiguration.toLogFriendlyString(config));
+ }
+ // New active audio playback.
+ newActiveAudioPlaybackClientUids.add(config.getClientUid());
+ int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
+ if (index == 0) {
+ // It's the lastly played music app already. Skip updating.
+ continue;
+ } else if (index > 0) {
+ mSortedAudioPlaybackClientUids.remove(index);
+ }
+ mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
+ }
+ }
+ mActiveAudioPlaybackPlayerInterfaceIds.clear();
+ mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds;
+ }
+ for (int uid : newActiveAudioPlaybackClientUids) {
+ mListener.onAudioPlaybackStarted(uid);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+ * audio/video) The UID whose audio playback becomes active at the last comes first.
+ */
+ public IntArray getSortedAudioPlaybackClientUids() {
+ IntArray sortedAudioPlaybackClientUids = new IntArray();
+ synchronized (mLock) {
+ sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+ }
+ return sortedAudioPlaybackClientUids;
+ }
+
+ /**
+ * Returns if the audio playback is active for the uid.
+ */
+ public boolean isPlaybackActive(int uid) {
+ synchronized (mLock) {
+ return mActiveAudioPlaybackClientUids.contains(uid);
+ }
+ }
+
+ /**
+ * Cleans up the sorted list of audio playback client UIDs with given {@param
+ * mediaButtonSessionUid}.
+ * <p>UIDs whose audio playback started after the media button session's audio playback
+ * cannot be the lastly played media app. So they won't needed anymore.
+ *
+ * @param mediaButtonSessionUid UID of the media button session.
+ */
+ public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+ if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+ break;
+ }
+ if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) {
+ // Clean up unnecessary UIDs.
+ // It doesn't need to be managed profile aware because it's just to prevent
+ // the list from increasing indefinitely. The media button session updating
+ // shouldn't be affected by cleaning up.
+ mSortedAudioPlaybackClientUids.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dumps {@link AudioPlaybackMonitor}.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + "Audio playback (lastly played comes first)");
+ String indent = prefix + " ";
+ for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ pw.print(indent + "uid=" + uid + " packages=");
+ String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ for (int j = 0; j < packages.length; j++) {
+ pw.print(packages[j] + " ");
+ }
+ }
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 20663a0..7f75c83 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -66,12 +66,6 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
- * The length of time a session will still be considered active after
- * pausing in ms.
- */
- private static final int ACTIVE_BUFFER = 30000;
-
- /**
* The amount of time we'll send an assumed volume after the last volume
* command before reverting to the last reported volume.
*/
@@ -109,7 +103,6 @@
private int mRatingType;
private int mRepeatMode;
private boolean mShuffleModeEnabled;
- private long mLastActiveTime;
// End TransportPerformer fields
// Volume handling fields
@@ -130,7 +123,7 @@
private String mCallingPackage;
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
- ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
+ ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
mOwnerPid = ownerPid;
mOwnerUid = ownerUid;
mUserId = userId;
@@ -140,7 +133,7 @@
mSession = new SessionStub();
mSessionCb = new SessionCb(cb);
mService = service;
- mHandler = new MessageHandler(handler.getLooper());
+ mHandler = new MessageHandler(handlerLooper);
mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
@@ -211,6 +204,15 @@
}
/**
+ * Get the UID this session was created for.
+ *
+ * @return The UID for this session.
+ */
+ public int getUid() {
+ return mOwnerUid;
+ }
+
+ /**
* Get the user id this session was created for.
*
* @return The user id for this session.
@@ -244,7 +246,7 @@
public void adjustVolume(int direction, int flags, String packageName, int uid,
boolean useSuggested) {
int previousFlagPlaySound = flags & AudioManager.FLAG_PLAY_SOUND;
- if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
+ if (isPlaybackActive() || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
@@ -320,25 +322,13 @@
}
/**
- * Check if the session is currently performing playback. This will also
- * return true if the session was recently paused.
+ * Check if the session is currently performing playback.
*
- * @param includeRecentlyActive True if playback that was recently paused
- * should count, false if it shouldn't.
* @return True if the session is performing playback, false otherwise.
*/
- public boolean isPlaybackActive(boolean includeRecentlyActive) {
- int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
- if (MediaSession.isActiveState(state)) {
- return true;
- }
- if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
- long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
- if (inactiveTime < ACTIVE_BUFFER) {
- return true;
- }
- }
- return false;
+ public boolean isPlaybackActive() {
+ int state = mPlaybackState == null ? PlaybackState.STATE_NONE : mPlaybackState.getState();
+ return MediaSession.isActiveState(state);
}
/**
@@ -456,7 +446,7 @@
@Override
public String toString() {
- return mPackageName + "/" + mTag + " (uid=" + mUserId + ")";
+ return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
@@ -817,6 +807,12 @@
@Override
public void setMediaButtonReceiver(PendingIntent pi) {
mMediaButtonReceiver = pi;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -842,15 +838,19 @@
@Override
public void setPlaybackState(PlaybackState state) {
- int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState();
- int newState = state == null ? 0 : state.getState();
- if (MediaSession.isActiveState(oldState) && newState == PlaybackState.STATE_PAUSED) {
- mLastActiveTime = SystemClock.elapsedRealtime();
- }
+ int oldState = mPlaybackState == null
+ ? PlaybackState.STATE_NONE : mPlaybackState.getState();
+ int newState = state == null
+ ? PlaybackState.STATE_NONE : state.getState();
synchronized (mLock) {
mPlaybackState = state;
}
- mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index ea9128f..4bf9d8f 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -57,11 +57,13 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -85,7 +87,7 @@
*/
public class MediaSessionService extends SystemService implements Monitor {
private static final String TAG = "MediaSessionService";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Leave log for key event always.
private static final boolean DEBUG_KEY_EVENT = true;
@@ -114,6 +116,7 @@
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
private MediaSessionRecord mGlobalPrioritySession;
+ private AudioPlaybackMonitor mAudioPlaybackMonitor;
// Used to notify system UI when remote volume was changed. TODO find a
// better way to handle this.
@@ -134,6 +137,19 @@
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
+ mAudioPlaybackMonitor = new AudioPlaybackMonitor(getContext(), mAudioService,
+ new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+ @Override
+ public void onAudioPlaybackStarted(int uid) {
+ synchronized (mLock) {
+ FullUserRecord user =
+ getFullUserRecordLocked(UserHandle.getUserId(uid));
+ if (user != null) {
+ user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+ }
+ }
+ }
+ });
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
@@ -161,9 +177,10 @@
user.mPriorityStack.onSessionStateChange(record);
if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
mGlobalPrioritySession = record;
+ user.pushAddressedPlayerChangedLocked();
}
+ mHandler.postSessionsChanged(record.getUserId());
}
- mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
}
/**
@@ -180,18 +197,14 @@
}
}
- public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
- boolean updateSessions = false;
+ public void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
Log.d(TAG, "Unknown session changed playback state. Ignoring.");
return;
}
- updateSessions = user.mPriorityStack.onPlaystateChange(record, oldState, newState);
- }
- if (updateSessions) {
- mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
+ user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
}
}
@@ -332,6 +345,9 @@
}
if (mGlobalPrioritySession == session) {
mGlobalPrioritySession = null;
+ if (session.isActive() && user != null) {
+ user.pushAddressedPlayerChangedLocked();
+ }
}
try {
@@ -340,8 +356,7 @@
// ignore exceptions while destroying a session.
}
session.onDestroy();
-
- mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
+ mHandler.postSessionsChanged(session.getUserId());
}
private void enforcePackageName(String packageName, int uid) {
@@ -461,7 +476,7 @@
}
final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
- callerPackageName, cb, tag, this, mHandler);
+ callerPackageName, cb, tag, this, mHandler.getLooper());
try {
cb.asBinder().linkToDeath(session, 0);
} catch (RemoteException e) {
@@ -469,8 +484,7 @@
}
user.addSessionLocked(session);
-
- mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
+ mHandler.postSessionsChanged(userId);
if (DEBUG) {
Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
@@ -496,10 +510,6 @@
}
List<MediaSessionRecord> records = user.mPriorityStack.getActiveSessions(userId);
int size = records.size();
- if (size > 0 && records.get(0).isPlaybackActive(false)) {
- user.rememberMediaButtonReceiverLocked(records.get(0));
- }
- user.pushAddressedPlayerChangedLocked();
ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
for (int i = 0; i < size; i++) {
tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
@@ -536,6 +546,22 @@
}
}
+ /**
+ * Called when the media button receiver for the {@param record} is changed.
+ *
+ * @param record the media session whose media button receiver is updated.
+ */
+ public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ MediaSessionRecord mediaButtonSession =
+ user.mPriorityStack.getMediaButtonSession();
+ if (record == mediaButtonSession) {
+ user.rememberMediaButtonReceiverLocked(mediaButtonSession);
+ }
+ }
+ }
+
private String getCallingPackageName(int uid) {
String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
if (packages != null && packages.length > 0) {
@@ -568,10 +594,10 @@
* place makes more sense and increases the readability.</p>
* <p>The contents of this object is guarded by {@link #mLock}.
*/
- final class FullUserRecord {
+ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener {
private static final String COMPONENT_NAME_USER_ID_DELIM = ",";
private final int mFullUserId;
- private final MediaSessionStack mPriorityStack = new MediaSessionStack();
+ private final MediaSessionStack mPriorityStack;
private PendingIntent mLastMediaButtonReceiver;
private ComponentName mRestoredMediaButtonReceiver;
private int mRestoredMediaButtonReceiverUserId;
@@ -588,6 +614,7 @@
public FullUserRecord(int fullUserId) {
mFullUserId = fullUserId;
+ mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
// Restore the remembered media button receiver before the boot.
String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -603,15 +630,14 @@
}
public void destroySessionsForUserLocked(int userId) {
- List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, 0, userId);
+ List<MediaSessionRecord> sessions = mPriorityStack.getPriorityList(false, userId);
for (MediaSessionRecord session : sessions) {
MediaSessionService.this.destroySessionLocked(session);
}
}
public void addSessionLocked(MediaSessionRecord session) {
- mPriorityStack.addSession(session,
- mFullUserId == mFullUserIds.get(session.getUserId()));
+ mPriorityStack.addSession(session);
}
public void removeSessionLocked(MediaSessionRecord session) {
@@ -642,21 +668,41 @@
mPriorityStack.dump(pw, indent);
}
- // Remember the media button receiver and keep it in the persistent storage.
- private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
+ @Override
+ public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+ MediaSessionRecord newMediaButtonSession) {
+ if (DEBUG_KEY_EVENT) {
+ Log.d(TAG, "Media button session will be changed to " + newMediaButtonSession);
+ }
+ synchronized (mLock) {
+ if (oldMediaButtonSession != null) {
+ mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+ }
+ if (newMediaButtonSession != null) {
+ rememberMediaButtonReceiverLocked(newMediaButtonSession);
+ mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+ }
+ pushAddressedPlayerChangedLocked();
+ }
+ }
+
+ // Remember media button receiver and keep it in the persistent storage.
+ public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
PendingIntent receiver = record.getMediaButtonReceiver();
- if (receiver == null) {
- return;
- }
mLastMediaButtonReceiver = receiver;
- ComponentName component = receiver.getIntent().getComponent();
- if (component != null && record.getPackageName().equals(component.getPackageName())) {
- String componentName = component.flattenToString();
- Settings.Secure.putStringForUser(mContentResolver,
- Settings.System.MEDIA_BUTTON_RECEIVER,
- componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
- mFullUserId);
+ mRestoredMediaButtonReceiver = null;
+ String componentName = "";
+ if (receiver != null) {
+ ComponentName component = receiver.getIntent().getComponent();
+ if (component != null
+ && record.getPackageName().equals(component.getPackageName())) {
+ componentName = component.flattenToString();
+ }
}
+ Settings.Secure.putStringForUser(mContentResolver,
+ Settings.System.MEDIA_BUTTON_RECEIVER,
+ componentName + COMPONENT_NAME_USER_ID_DELIM + record.getUserId(),
+ mFullUserId);
}
private void pushAddressedPlayerChangedLocked() {
@@ -682,14 +728,8 @@
}
private MediaSessionRecord getMediaButtonSessionLocked() {
- if (isGlobalPriorityActiveLocked()) {
- return mGlobalPrioritySession;
- }
- // If we don't have a media button receiver to fall back on
- // include non-playing sessions for dispatching.
- boolean useNotPlayingSessions = (mLastMediaButtonReceiver == null
- && mRestoredMediaButtonReceiver == null);
- return mPriorityStack.getDefaultMediaButtonSession(useNotPlayingSessions);
+ return isGlobalPriorityActiveLocked()
+ ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
}
}
@@ -1262,6 +1302,7 @@
for (int i = 0; i < count; i++) {
mUserRecords.valueAt(i).dumpLocked(pw, "");
}
+ mAudioPlaybackMonitor.dump(pw, "");
}
}
@@ -1390,7 +1431,7 @@
PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver;
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent
- + " to the last known pendingIntent " + receiver);
+ + " to the last known PendingIntent " + receiver);
}
receiver.send(getContext(),
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
@@ -1413,7 +1454,8 @@
}
mediaButtonIntent.setComponent(receiver);
getContext().sendBroadcastAsUser(mediaButtonIntent,
- UserHandle.of(mCurrentFullUserRecord.mRestoredMediaButtonReceiverUserId));
+ UserHandle.of(mCurrentFullUserRecord
+ .mRestoredMediaButtonReceiverUserId));
if (mCurrentFullUserRecord.mCallback != null) {
mCurrentFullUserRecord.mCallback
.onMediaKeyEventDispatchedToMediaButtonReceiver(
@@ -1426,23 +1468,6 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
}
- } else {
- if (DEBUG) {
- Log.d(TAG, "Sending media key ordered broadcast");
- }
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- // Fallback to legacy behavior
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- if (needWakeLock) {
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
- }
- // Send broadcast only to the full user.
- getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT,
- null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
}
}
@@ -1637,12 +1662,13 @@
final class MessageHandler extends Handler {
private static final int MSG_SESSIONS_CHANGED = 1;
private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+ private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SESSIONS_CHANGED:
- pushSessionsChanged(msg.arg1);
+ pushSessionsChanged((int) msg.obj);
break;
case MSG_VOLUME_INITIAL_DOWN:
synchronized (mLock) {
@@ -1657,8 +1683,15 @@
}
}
- public void post(int what, int arg1, int arg2) {
- obtainMessage(what, arg1, arg2).sendToTarget();
+ public void postSessionsChanged(int userId) {
+ // Use object instead of the arguments when posting message to remove pending requests.
+ Integer userIdInteger = mIntegerCache.get(userId);
+ if (userIdInteger == null) {
+ userIdInteger = Integer.valueOf(userId);
+ mIntegerCache.put(userId, userIdInteger);
+ }
+ removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
+ obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 8b80734..b0d8adc 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,12 +16,13 @@
package com.android.server.media;
-import android.app.ActivityManager;
import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.PlaybackState;
import android.media.session.MediaSession;
-import android.os.RemoteException;
+import android.media.session.PlaybackState;
+import android.os.Debug;
import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -33,6 +34,20 @@
* <p>This class isn't thread-safe. The caller should take care of the synchronization.
*/
class MediaSessionStack {
+ private static final boolean DEBUG = MediaSessionService.DEBUG;
+ private static final String TAG = "MediaSessionStack";
+
+ /**
+ * Listens the change in the media button session.
+ */
+ interface OnMediaButtonSessionChangedListener {
+ /**
+ * Called when the media button session is changed.
+ */
+ void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
+ MediaSessionRecord newMediaButtonSession);
+ }
+
/**
* These are states that usually indicate the user took an action and should
* bump priority regardless of the old state.
@@ -51,57 +66,45 @@
PlaybackState.STATE_CONNECTING,
PlaybackState.STATE_PLAYING };
- private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+ /**
+ * Sorted list of the media sessions.
+ * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
+ * TRANSITION_PRIORITY_STATES comes first.
+ * @see #shouldUpdatePriority
+ */
+ private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
- // The last record that either entered one of the playing states or was
- // added.
- private MediaSessionRecord mLastInterestingRecord;
- private MediaSessionRecord mCachedButtonReceiver;
+ private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
+
+ /**
+ * The media button session which receives media key events.
+ * It could be null if the previous media buttion session is released.
+ */
+ private MediaSessionRecord mMediaButtonSession;
+
private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
private ArrayList<MediaSessionRecord> mCachedActiveList;
- private ArrayList<MediaSessionRecord> mCachedTransportControlList;
- /**
- * Checks if a media session is created from the most recent app.
- *
- * @param record A media session record to be examined.
- * @return {@code true} if the media session's package name equals to the most recent app, false
- * otherwise.
- */
- private static boolean isFromMostRecentApp(MediaSessionRecord record) {
- try {
- List<ActivityManager.RecentTaskInfo> tasks =
- ActivityManager.getService().getRecentTasks(1,
- ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
- ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES |
- ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId()).getList();
- if (tasks != null && !tasks.isEmpty()) {
- ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
- if (recentTask.userId == record.getUserId() && recentTask.baseIntent != null) {
- return recentTask.baseIntent.getComponent().getPackageName()
- .equals(record.getPackageName());
- }
- }
- } catch (RemoteException e) {
- return false;
- }
- return false;
+ MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+ mAudioPlaybackMonitor = monitor;
+ mOnMediaButtonSessionChangedListener = listener;
}
/**
* Add a record to the priority tracker.
*
* @param record The record to add.
- * @param fromForegroundUser {@code true} if the session is created by the foreground user.
*/
- public void addSession(MediaSessionRecord record, boolean fromForegroundUser) {
+ public void addSession(MediaSessionRecord record) {
mSessions.add(record);
clearCache();
- if (fromForegroundUser && isFromMostRecentApp(record)) {
- mLastInterestingRecord = record;
- }
+
+ // Update the media button session.
+ // The added session could be the session from the package with the audio playback.
+ // This can happen if an app starts audio playback before creating media session.
+ updateMediaButtonSessionIfNeeded();
}
/**
@@ -111,6 +114,11 @@
*/
public void removeSession(MediaSessionRecord record) {
mSessions.remove(record);
+ if (mMediaButtonSession == record) {
+ // When the media button session is gone, try to find the alternative media session
+ // in the media button session app.
+ onMediaSessionChangeInMediaButtonSessionApp();
+ }
clearCache();
}
@@ -122,32 +130,33 @@
}
/**
- * Notify the priority tracker that a session's state changed.
+ * Notify the priority tracker that a session's playback state changed.
*
* @param record The record that changed.
* @param oldState Its old playback state.
* @param newState Its new playback state.
- * @return true if the priority order was updated, false otherwise.
*/
- public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
+ public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
if (shouldUpdatePriority(oldState, newState)) {
mSessions.remove(record);
mSessions.add(0, record);
clearCache();
- // This becomes the last interesting record since it entered a
- // playing state
- mLastInterestingRecord = record;
- return true;
} else if (!MediaSession.isActiveState(newState)) {
// Just clear the volume cache when a state goes inactive
mCachedVolumeDefault = null;
}
- return false;
+
+ // In most cases, playback state isn't needed for finding media buttion session,
+ // but we only use it as a hint if an app has multiple local media sessions.
+ // In that case, we pick the media session whose PlaybackState matches
+ // the audio playback configuration.
+ if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
+ onMediaSessionChangeInMediaButtonSessionApp();
+ }
}
/**
- * Handle any stack changes that need to occur in response to a session
- * state change. TODO add the old and new session state as params
+ * Handle the change in activeness for a session.
*
* @param record The record that changed.
*/
@@ -158,6 +167,81 @@
}
/**
+ * Update the media button session if needed.
+ * <p>The media button session is the session that will receive the media button events.
+ * <p>We send the media button events to the lastly played app. If the app has the media
+ * session, the session will receive the media button events.
+ */
+ public void updateMediaButtonSessionIfNeeded() {
+ if (DEBUG) {
+ Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
+ }
+ IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+ for (int i = 0; i < audioPlaybackUids.size(); i++) {
+ MediaSessionRecord mediaButtonSession =
+ findMediaButtonSession(audioPlaybackUids.get(i));
+ if (mediaButtonSession != null) {
+ // Found the media button session.
+ mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+ if (mMediaButtonSession != mediaButtonSession) {
+ mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
+ mMediaButtonSession, mediaButtonSession);
+ mMediaButtonSession = mediaButtonSession;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Handle the change in a media session in the media button session app.
+ * <p>If the app has multiple media sessions, change in a media sesion in the app may change
+ * the media button session.
+ * @see #findMediaButtonSession
+ */
+ private void onMediaSessionChangeInMediaButtonSessionApp() {
+ MediaSessionRecord newMediaButtonSession =
+ findMediaButtonSession(mMediaButtonSession.getUid());
+ if (newMediaButtonSession != mMediaButtonSession) {
+ mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(mMediaButtonSession,
+ newMediaButtonSession);
+ mMediaButtonSession = newMediaButtonSession;
+ }
+ }
+
+ /**
+ * Find the media button session with the given {@param uid}.
+ * If the app has multiple media sessions, the media session matches the audio playback state
+ * becomes the media button session.
+ *
+ * @return The media button session. Returns {@code null} if the app doesn't have a media
+ * session.
+ */
+ private MediaSessionRecord findMediaButtonSession(int uid) {
+ MediaSessionRecord mediaButtonSession = null;
+ for (MediaSessionRecord session : mSessions) {
+ // Since the media buttons come with the headset/speaker, users will expect changes in
+ // the headset/speaker when they press media buttons. So only consider local playback
+ // for the media button session.
+ if (uid == session.getUid()
+ && session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+ if (session.isPlaybackActive() ==
+ mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+ // If there's a media session whose PlaybackState matches
+ // the audio playback state, return it immediately.
+ return session;
+ }
+ if (mediaButtonSession == null) {
+ // Among the media sessions whose PlaybackState doesn't match
+ // the audio playback state, pick the top priority.
+ mediaButtonSession = session;
+ }
+ }
+ }
+ return mediaButtonSession;
+ }
+
+ /**
* Get the current priority sorted list of active sessions. The most
* important session is at index 0 and the least important at size - 1.
*
@@ -166,57 +250,29 @@
*/
public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
if (mCachedActiveList == null) {
- mCachedActiveList = getPriorityList(true, 0, userId);
+ mCachedActiveList = getPriorityList(true, userId);
}
return mCachedActiveList;
}
/**
- * Get the highest priority session that can handle media buttons.
+ * Get the media button session which receives the media button events.
*
- * @param includeNotPlaying Return a non-playing session if nothing else is
- * available
- * @return The default media button session or null.
+ * @return The media button session or null.
*/
- public MediaSessionRecord getDefaultMediaButtonSession(boolean includeNotPlaying) {
- if (mCachedButtonReceiver != null) {
- return mCachedButtonReceiver;
- }
- ArrayList<MediaSessionRecord> records = getPriorityList(true,
- MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, UserHandle.USER_ALL);
- if (records.size() > 0) {
- MediaSessionRecord record = records.get(0);
- if (record.isPlaybackActive(false)) {
- // Since we're going to send a button event to this record make
- // it the last interesting one.
- mLastInterestingRecord = record;
- mCachedButtonReceiver = record;
- } else if (mLastInterestingRecord != null) {
- if (records.contains(mLastInterestingRecord)) {
- mCachedButtonReceiver = mLastInterestingRecord;
- } else {
- // That record is no longer used. Clear its reference.
- mLastInterestingRecord = null;
- }
- }
- if (includeNotPlaying && mCachedButtonReceiver == null) {
- // If we really want a record and we didn't find one yet use the
- // highest priority session even if it's not playing.
- mCachedButtonReceiver = record;
- }
- }
- return mCachedButtonReceiver;
+ public MediaSessionRecord getMediaButtonSession() {
+ return mMediaButtonSession;
}
public MediaSessionRecord getDefaultVolumeSession() {
if (mCachedVolumeDefault != null) {
return mCachedVolumeDefault;
}
- ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, UserHandle.USER_ALL);
+ ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
int size = records.size();
for (int i = 0; i < size; i++) {
MediaSessionRecord record = records.get(i);
- if (record.isPlaybackActive(false)) {
+ if (record.isPlaybackActive()) {
mCachedVolumeDefault = record;
return record;
}
@@ -225,7 +281,7 @@
}
public MediaSessionRecord getDefaultRemoteSession(int userId) {
- ArrayList<MediaSessionRecord> records = getPriorityList(true, 0, userId);
+ ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
int size = records.size();
for (int i = 0; i < size; i++) {
@@ -238,9 +294,10 @@
}
public void dump(PrintWriter pw, String prefix) {
- ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false, 0,
+ ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
UserHandle.USER_ALL);
int count = sortedSessions.size();
+ pw.println(prefix + "Media button session is " + mMediaButtonSession);
pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
String indent = prefix + " ";
for (int i = 0; i < count; i++) {
@@ -252,22 +309,23 @@
/**
* Get a priority sorted list of sessions. Can filter to only return active
- * sessions or sessions with specific flags.
+ * sessions or sessions.
+ * <p>Here's the priority order.
+ * <li>System priority session (session with FLAG_EXCLUSIVE_GLOBAL_PRIORITY)</li>
+ * <li>Active sessions whose PlaybackState is active</li>
+ * <li>Active sessions whose PlaybackState is inactive</li>
+ * <li>Inactive sessions</li>
*
* @param activeOnly True to only return active sessions, false to return
* all sessions.
- * @param withFlags Only return sessions with all the specified flags set. 0
- * returns all sessions.
* @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
* will return sessions for all users.
* @return The priority sorted list of sessions.
*/
- public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int withFlags,
- int userId) {
+ public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
- int lastLocalIndex = 0;
+ int lastPlaybackActiveIndex = 0;
int lastActiveIndex = 0;
- int lastPublishedIndex = 0;
int size = mSessions.size();
for (int i = 0; i < size; i++) {
@@ -277,10 +335,7 @@
// Filter out sessions for the wrong user
continue;
}
- if ((session.getFlags() & withFlags) != withFlags) {
- // Filter out sessions with the wrong flags
- continue;
- }
+
if (!session.isActive()) {
if (!activeOnly) {
// If we're getting unpublished as well always put them at
@@ -294,28 +349,13 @@
// System priority sessions are special and always go at the
// front. We expect there to only be one of these at a time.
result.add(0, session);
- lastLocalIndex++;
+ lastPlaybackActiveIndex++;
lastActiveIndex++;
- lastPublishedIndex++;
- } else if (session.isPlaybackActive(true)) {
- // TODO this with real local route check
- if (true) {
- // Active local sessions get top priority
- result.add(lastLocalIndex, session);
- lastLocalIndex++;
- lastActiveIndex++;
- lastPublishedIndex++;
- } else {
- // Then active remote sessions
- result.add(lastActiveIndex, session);
- lastActiveIndex++;
- lastPublishedIndex++;
- }
+ } else if (session.isPlaybackActive()) {
+ result.add(lastPlaybackActiveIndex++, session);
+ lastActiveIndex++;
} else {
- // inactive sessions go at the end in order of whoever last did
- // something.
- result.add(lastPublishedIndex, session);
- lastPublishedIndex++;
+ result.add(lastActiveIndex++, session);
}
}
@@ -345,8 +385,6 @@
private void clearCache() {
mCachedDefault = null;
mCachedVolumeDefault = null;
- mCachedButtonReceiver = null;
mCachedActiveList = null;
- mCachedTransportControlList = null;
}
}
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 41c5331..82dd9ac 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -197,7 +197,7 @@
.setQuota(CacheQuotaHint.QUOTA_NOT_SET)
.build());
} catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Unable to find package for quota calculation", e);
+ // This may happen if an app has a recorded usage, but has been uninstalled.
continue;
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index ebbb8b3..42f12eb 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -16,6 +16,7 @@
package com.android.server.tv;
+import android.hardware.tv.input.V1_0.Constants;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvStreamConfig;
import android.os.Handler;
@@ -41,9 +42,10 @@
public final static int ERROR_STALE_CONFIG = -2;
public final static int ERROR_UNKNOWN = -3;
- public static final int EVENT_DEVICE_AVAILABLE = 1;
- public static final int EVENT_DEVICE_UNAVAILABLE = 2;
- public static final int EVENT_STREAM_CONFIGURATION_CHANGED = 3;
+ public static final int EVENT_DEVICE_AVAILABLE = Constants.EVENT_DEVICE_AVAILABLE;
+ public static final int EVENT_DEVICE_UNAVAILABLE = Constants.EVENT_DEVICE_UNAVAILABLE;
+ public static final int EVENT_STREAM_CONFIGURATION_CHANGED =
+ Constants.EVENT_STREAM_CONFIGURATIONS_CHANGED;
public static final int EVENT_FIRST_FRAME_CAPTURED = 4;
public interface Callback {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 72ae90d..1decf4e 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
import static android.app.ActivityManager.StackId;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -1204,12 +1205,23 @@
* in anyway.
*/
@Override
- int getOrientation() {
+ int getOrientation(int candidate) {
+ if (!fillsParent()) {
+ // Can't specify orientation if app doesn't fill parent.
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ if (candidate == SCREEN_ORIENTATION_BEHIND) {
+ // Allow app to specify orientation regardless of its visibility state if the current
+ // candidate want us to use orientation behind. I.e. the visible app on-top of this one
+ // wants us to use the orientation of the app behind it.
+ return mOrientation;
+ }
+
// The {@link AppWindowToken} should only specify an orientation when it is not closing or
// going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
// an Activity in another task being started in the wrong orientation during the transition.
- if (fillsParent()
- && !(sendingToBottom || mService.mClosingApps.contains(this))
+ if (!(sendingToBottom || mService.mClosingApps.contains(this))
&& (isVisible() || mService.mOpeningApps.contains(this))) {
return mOrientation;
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 012480e..3ce61f0 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -195,7 +195,8 @@
* @return whether the given {@param aspectRatio} is valid.
*/
public boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
- return mMinAspectRatio <= aspectRatio && aspectRatio <= mMaxAspectRatio;
+ return Float.compare(mMinAspectRatio, aspectRatio) <= 0 &&
+ Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 68d0f24..b0e3e32 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,8 +152,7 @@
}
WindowState computeFocusedWindow() {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
final WindowState win = dc.findFocusedWindow();
if (win != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2a02359..84ba139 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -516,14 +516,22 @@
mOrientation = orientation;
}
+ int getOrientation() {
+ return getOrientation(mOrientation);
+ }
+
/**
* Returns the specified orientation for this window container or one of its children is there
* is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no
* specification is set.
* NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a
* specification...
+ *
+ * @param candidate The current orientation candidate that will be returned if we don't find a
+ * better match.
+ * @return The orientation as specified by this branch or the window hierarchy.
*/
- int getOrientation() {
+ int getOrientation(int candidate) {
if (!fillsParent()) {
// Ignore containers that don't completely fill their parents.
return SCREEN_ORIENTATION_UNSET;
@@ -537,12 +545,14 @@
&& mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
return mOrientation;
}
- int candidate = mOrientation;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- final int orientation = wc.getOrientation();
+ // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
+ // SCREEN_ORIENTATION_UNSPECIFIED?
+ final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
+ ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
if (orientation == SCREEN_ORIENTATION_BEHIND) {
// container wants us to use the orientation of the container behind it. See if we
// can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index b433350..78c0fa7 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -577,7 +577,6 @@
}
Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
- // TODO(b/32200867): Ensure the event type values are in sync with the framework code.
mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
return Void();
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 7b4fa87..590bce1 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -608,7 +608,7 @@
}
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- if (args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+ if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
// Execute confirmConfiguration() and take no further action.
confirmConfiguration();
return;
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 2a8f4a3..bb4507d 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -52,6 +52,7 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index a9c69f6..e0ac393 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -128,9 +128,11 @@
private static final ScoredNetwork SCORED_NETWORK_2 =
new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
null /* rssiCurve*/);
+ private static final String NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
+ "networkAvailableNotificationChannelId";
private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
1, RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
- USE_WIFI_ENABLE_ACTIVITY_COMP);
+ USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
@Mock private NetworkScorerAppManager mNetworkScorerAppManager;
@Mock private Context mContext;
@@ -965,7 +967,7 @@
.thenReturn(PackageManager.PERMISSION_GRANTED);
NetworkScorerAppData expectedAppData = new NetworkScorerAppData(Binder.getCallingUid(),
RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
- USE_WIFI_ENABLE_ACTIVITY_COMP);
+ USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
bindToScorer(expectedAppData);
assertEquals(expectedAppData, mNetworkScoreService.getActiveScorer());
}
@@ -1007,7 +1009,7 @@
final int callingUid = callerIsScorer ? Binder.getCallingUid() : Binder.getCallingUid() + 1;
NetworkScorerAppData appData = new NetworkScorerAppData(callingUid,
RECOMMENDATION_SERVICE_COMP, RECOMMENDATION_SERVICE_LABEL,
- USE_WIFI_ENABLE_ACTIVITY_COMP);
+ USE_WIFI_ENABLE_ACTIVITY_COMP, NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
bindToScorer(appData);
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
index e8663a2..9197ccf9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
@@ -62,6 +62,8 @@
public class NetworkScorerAppManagerTest {
private static String MOCK_SERVICE_LABEL = "Mock Service";
private static String MOCK_OVERRIDEN_SERVICE_LABEL = "Mock Service Label Override";
+ private static String MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
+ "Mock Network Available Notification Channel Id";
@Mock private Context mMockContext;
@Mock private PackageManager mMockPm;
@@ -168,13 +170,30 @@
mockScoreNetworksGranted(recoComponent.getPackageName());
mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
- mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
assertEquals(924, activeScorer.packageUid);
assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity());
+ assertNull(activeScorer.getNetworkAvailableNotificationChannelId());
+ }
+
+ @Test
+ public void testGetActiveScorer_providerAvailable_networkAvailableNotificationChannelIdSet() {
+ final ComponentName recoComponent = new ComponentName("package1", "class1");
+ setNetworkRecoPackageSetting(recoComponent.getPackageName());
+ mockScoreNetworksGranted(recoComponent.getPackageName());
+ mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ null /* enableUseOpenWifiActivityPackage */, false /* serviceLabelOverride */,
+ true /* setNotificationChannelId */);
+
+ final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+ assertNotNull(activeScorer);
+ assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
+ assertEquals(924, activeScorer.packageUid);
+ assertEquals(MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID,
+ activeScorer.getNetworkAvailableNotificationChannelId());
}
@Test
@@ -368,6 +387,13 @@
private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid,
String enableUseOpenWifiActivityPackage, boolean serviceLabelOverride) {
+ mockRecommendationServiceAvailable(compName, packageUid, enableUseOpenWifiActivityPackage,
+ serviceLabelOverride, false);
+ }
+
+ private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid,
+ String enableUseOpenWifiActivityPackage, boolean serviceLabelOverride,
+ boolean setNotificationChannel) {
final ResolveInfo serviceInfo = new ResolveInfo();
serviceInfo.serviceInfo = new ServiceInfo();
serviceInfo.serviceInfo.name = compName.getClassName();
@@ -390,6 +416,14 @@
} else {
serviceInfo.serviceInfo.nonLocalizedLabel = MOCK_SERVICE_LABEL;
}
+ if (setNotificationChannel) {
+ if (serviceInfo.serviceInfo.metaData == null) {
+ serviceInfo.serviceInfo.metaData = new Bundle();
+ }
+ serviceInfo.serviceInfo.metaData.putString(
+ NetworkScoreManager.NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID_META_DATA,
+ MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID);
+ }
final int flags = PackageManager.GET_META_DATA;
when(mMockPm.resolveService(
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 374aee1..00f6273 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -94,7 +94,7 @@
* <p>Run with:<pre>
* mmma -j40 frameworks/base/services/tests/servicestests
* adb install -r ${OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- * adb shell am instrument -w -e class package com.android.server.accounts \
+ * adb shell am instrument -w -e package com.android.server.accounts \
* com.android.frameworks.servicestests\
* /android.support.test.runner.AndroidJUnitRunner
* </pre>
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 2cb8af4..aa37407 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -385,6 +385,42 @@
}
@Test
+ public void testFindAllVisibilityValues() {
+ long accId = 10;
+ long accId2 = 11;
+ String packageName1 = "com.example.one";
+ String packageName2 = "com.example.two";
+ Account account = new Account("name", "example.com");
+ Account account2 = new Account("name2", "example2.com");
+ assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
+
+ mAccountsDb.insertDeAccount(account, accId);
+ assertNull(mAccountsDb.findAccountVisibility(account, packageName1));
+ assertNull(mAccountsDb.findAccountVisibility(accId, packageName1));
+ mAccountsDb.insertDeAccount(account2, accId2);
+
+ mAccountsDb.setAccountVisibility(accId, packageName1, 1);
+ mAccountsDb.setAccountVisibility(accId, packageName2, 2);
+ mAccountsDb.setAccountVisibility(accId2, packageName1, 1);
+
+ Map<Account, Map<String, Integer>> vis = mAccountsDb.findAllVisibilityValues();
+ assertEquals(vis.size(), 2);
+ Map<String, Integer> accnt1Visibility = vis.get(account);
+ assertEquals(accnt1Visibility.size(), 2);
+ assertEquals(accnt1Visibility.get(packageName1), Integer.valueOf(1));
+ assertEquals(accnt1Visibility.get(packageName2), Integer.valueOf(2));
+ Map<String, Integer> accnt2Visibility = vis.get(account2);
+ assertEquals(accnt2Visibility.size(), 1);
+ assertEquals(accnt2Visibility.get(packageName1), Integer.valueOf(1));
+
+ mAccountsDb.setAccountVisibility(accId2, packageName2, 3);
+ vis = mAccountsDb.findAllVisibilityValues();
+ accnt2Visibility = vis.get(account2);
+ assertEquals(accnt2Visibility.size(), 2);
+ assertEquals(accnt2Visibility.get(packageName2), Integer.valueOf(3));
+ }
+
+ @Test
public void testVisibilityCleanupTrigger() {
long accId = 10;
String packageName1 = "com.example.one";
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index b12da34..1e038df 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -51,7 +51,9 @@
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -116,7 +118,9 @@
UidRecord.CHANGE_ACTIVE
};
+ @Mock private Context mContext;
@Mock private AppOpsService mAppOpsService;
+ @Mock private PackageManager mPackageManager;
private TestInjector mInjector;
private ActivityManagerService mAms;
@@ -133,6 +137,8 @@
mInjector = new TestInjector();
mAms = new ActivityManagerService(mInjector);
mAms.mWaitForNetworkTimeoutMs = 100;
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
}
@After
@@ -601,10 +607,12 @@
uidRecord.pendingChange = changeItem;
uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+ }
+ @Test
+ public void testEnqueueUidChangeLocked_nullUidRecord() {
// Use "null" uidRecord to make sure there is no crash.
- // TODO: currently it crashes, uncomment after fixing it.
- // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+ mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
}
private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
@@ -770,6 +778,11 @@
private boolean mRestricted = true;
@Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
public AppOpsService getAppOpsService(File file, Handler handler) {
return mAppOpsService;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index b5826f0..2003b91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -26,8 +26,10 @@
import android.view.Surface;
import android.view.WindowManager;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -160,4 +162,22 @@
sWm.mRoot.mOrientationChangeComplete = true;
sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
}
+
+ @Test
+ public void testGetOrientation() throws Exception {
+ final TestAppWindowToken token = new TestAppWindowToken(sDisplayContent);
+ token.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ token.setFillsParent(false);
+ // Can not specify orientation if app doesn't fill parent.
+ assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+
+ token.setFillsParent(true);
+ token.hidden = true;
+ token.sendingToBottom = true;
+ // Can not specify orientation if app isn't visible even though it fills parent.
+ assertEquals(SCREEN_ORIENTATION_UNSET, token.getOrientation());
+ // Can specify orientation if the current orientation candidate is orientation behind.
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, token.getOrientation(SCREEN_ORIENTATION_BEHIND));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 3868242..dd94a21 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -233,6 +234,26 @@
assertEquals(currentOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
+ @Test
+ public void testFocusedWindowMultipleDisplays() throws Exception {
+ // Create a focusable window and check that focus is calcualted correctly
+ final WindowState window1 =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "window1");
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+ // Check that a new display doesn't affect focus
+ final DisplayContent dc = createNewDisplay();
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+
+ // Add a window to the second display, and it should be focused
+ final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+ assertEquals(window2, sWm.mRoot.computeFocusedWindow());
+
+ // Move the first window to the to including parents, and make sure focus is updated
+ window1.getParent().positionChildAt(POSITION_TOP, window1, true);
+ assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+ }
+
private void assertForAllWindowsOrder(List<WindowState> expectedWindows) {
final LinkedList<WindowState> actualWindows = new LinkedList();
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
index 3ce3df1..9dbd8a6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -76,9 +76,9 @@
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
sWm.mClosingApps.add(appWindowToken2);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
}
@Test
@@ -94,9 +94,9 @@
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, stack.getOrientation());
task2.setSendingToBottom(true);
- assertEquals(stack.getOrientation(), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, stack.getOrientation());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 80b2e7d..a7d594c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -422,7 +422,7 @@
final TestWindowContainer child1 = root.addChildWindow(builder);
child1.setFillsParent(true);
- assertTrue(root.getOrientation() == expectedOrientation);
+ assertEquals(expectedOrientation, root.getOrientation());
}
@Test
@@ -805,8 +805,13 @@
}
@Override
+ int getOrientation(int candidate) {
+ return mOrientation != null ? mOrientation : super.getOrientation(candidate);
+ }
+
+ @Override
int getOrientation() {
- return mOrientation != null ? mOrientation : super.getOrientation();
+ return getOrientation(super.mOrientation);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 48799d2..a9d930f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.view.View.VISIBLE;
+
import android.app.ActivityManager.TaskDescription;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -233,7 +235,7 @@
attrs.setTitle(name);
final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE,
- 0, attrs, 0, 0, ownerCanAddInternalSystemWindow);
+ 0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
// adding it to the token...
token.addWindow(w);