Merge "Fix warnings in Bridge"
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..9a7d0a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3673,7 +3673,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -12695,7 +12695,7 @@
enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
}
- public class Color {
+ public final class Color {
ctor public Color();
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
@@ -12720,6 +12720,7 @@
method public float getComponent(int);
method public int getComponentCount();
method public float[] getComponents();
+ method public float[] getComponents(float[]);
method public android.graphics.ColorSpace.Model getModel();
method public float green();
method public static float green(long);
@@ -21818,23 +21819,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 +21848,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 +22291,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 +23602,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 +24338,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 {
@@ -24548,6 +24560,7 @@
field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
@@ -24625,6 +24638,7 @@
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -31884,6 +31898,7 @@
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -34858,7 +34873,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 +37857,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 {
@@ -39363,6 +39379,7 @@
field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+ field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -41062,9 +41079,9 @@
public static final class FontConfig.Font implements android.os.Parcelable {
method public int describeContents();
method public android.text.FontConfig.Axis[] getAxes();
- method public android.os.ParcelFileDescriptor getFd();
method public java.lang.String getFontName();
method public int getTtcIndex();
+ method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
method public void writeToParcel(android.os.Parcel, int);
@@ -45476,7 +45493,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/removed.txt b/api/removed.txt
index 8acf4ad..75da976 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 48b878e..0b8017d 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";
@@ -3798,7 +3799,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -11171,6 +11172,7 @@
method public abstract byte[] getInstantAppCookie();
method public abstract int getInstantAppCookieMaxSize();
method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+ method public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
method public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
@@ -11420,6 +11422,7 @@
field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+ field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_INSTANT = 8388608; // 0x800000
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
@@ -13420,7 +13423,7 @@
enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
}
- public class Color {
+ public final class Color {
ctor public Color();
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
@@ -13445,6 +13448,7 @@
method public float getComponent(int);
method public int getComponentCount();
method public float[] getComponents();
+ method public float[] getComponents(float[]);
method public android.graphics.ColorSpace.Model getModel();
method public float green();
method public static float green(long);
@@ -23604,23 +23608,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 +23637,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 +24080,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 +25402,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 +26210,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 {
@@ -34716,6 +34731,7 @@
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -37748,7 +37764,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 +37813,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 +37885,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 +40990,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 {
@@ -42717,6 +42732,7 @@
field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+ field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -44203,6 +44219,7 @@
method public byte[] getInstantAppCookie();
method public int getInstantAppCookieMaxSize();
method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+ method public android.content.ComponentName getInstantAppResolverSettingsComponent();
method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
@@ -44516,9 +44533,9 @@
public static final class FontConfig.Font implements android.os.Parcelable {
method public int describeContents();
method public android.text.FontConfig.Axis[] getAxes();
- method public android.os.ParcelFileDescriptor getFd();
method public java.lang.String getFontName();
method public int getTtcIndex();
+ method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
method public void writeToParcel(android.os.Parcel, int);
@@ -48931,7 +48948,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a2fcbcd..3aa9398 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -374,16 +374,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4d8d7f2..10cf5ce 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3675,7 +3675,7 @@
method public void onLowMemory();
method public boolean onMenuItemSelected(int, android.view.MenuItem);
method public boolean onMenuOpened(int, android.view.Menu);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method public void onMultiWindowModeChanged(boolean);
method public boolean onNavigateUp();
method public boolean onNavigateUpFromChild(android.app.Activity);
@@ -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);
@@ -12743,7 +12745,7 @@
enum_constant public static final android.graphics.Canvas.VertexMode TRIANGLE_STRIP;
}
- public class Color {
+ public final class Color {
ctor public Color();
method public static int HSVToColor(float[]);
method public static int HSVToColor(int, float[]);
@@ -12768,6 +12770,7 @@
method public float getComponent(int);
method public int getComponentCount();
method public float[] getComponents();
+ method public float[] getComponents(float[]);
method public android.graphics.ColorSpace.Model getModel();
method public float green();
method public static float green(long);
@@ -21929,23 +21932,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 +21961,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 +22404,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 +23715,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 +24451,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 {
@@ -24659,6 +24673,7 @@
field public static final java.lang.String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
field public static final java.lang.String COLUMN_STARTING_PRICE = "starting_price";
field public static final java.lang.String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String INTERACTION_TYPE_FANS = "INTERACTION_TYPE_FANS";
field public static final java.lang.String INTERACTION_TYPE_FOLLOWERS = "INTERACTION_TYPE_FOLLOWERS";
@@ -24736,6 +24751,7 @@
field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+ field public static final java.lang.String COLUMN_TRANSIENT = "transient";
field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
field public static final java.lang.String COLUMN_TYPE = "type";
field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -32019,6 +32035,7 @@
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
@@ -34997,7 +35014,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 +38060,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 {
@@ -39564,6 +39582,7 @@
field public static final java.lang.String KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array";
field public static final java.lang.String KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool";
field public static final java.lang.String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
+ field public static final java.lang.String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final java.lang.String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
@@ -41267,9 +41286,9 @@
public static final class FontConfig.Font implements android.os.Parcelable {
method public int describeContents();
method public android.text.FontConfig.Axis[] getAxes();
- method public android.os.ParcelFileDescriptor getFd();
method public java.lang.String getFontName();
method public int getTtcIndex();
+ method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
method public void writeToParcel(android.os.Parcel, int);
@@ -45849,7 +45868,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method protected void onLayout(boolean, int, int, int, int);
method protected void onMeasure(int, int);
- method public void onMovedToDisplay(int);
+ method public void onMovedToDisplay(int, android.content.res.Configuration);
method protected void onOverScrolled(int, int, boolean, boolean);
method public void onPointerCaptureChange(boolean);
method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 8acf4ad..75da976 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -380,16 +380,6 @@
}
-package android.view.textclassifier {
-
- public abstract interface TextClassifier {
- method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
- method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
- method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
- }
-
-}
-
package android.webkit {
public class WebViewClient {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index db3772d..658d662 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -94,6 +94,8 @@
runGetFbeMode();
} else if ("fstrim".equals(op)) {
runFstrim();
+ } else if ("set-virtual-disk".equals(op)) {
+ runSetVirtualDisk();
} else {
throw new IllegalArgumentException();
}
@@ -225,6 +227,12 @@
mSm.fstrim(0);
}
+ public void runSetVirtualDisk() throws RemoteException {
+ final boolean virtualDisk = Boolean.parseBoolean(nextArg());
+ mSm.setDebugFlags(virtualDisk ? StorageManager.DEBUG_VIRTUAL_DISK : 0,
+ StorageManager.DEBUG_VIRTUAL_DISK);
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -240,6 +248,7 @@
System.err.println(" sm has-adoptable");
System.err.println(" sm get-primary-storage-uuid");
System.err.println(" sm set-force-adoptable [true|false]");
+ System.err.println(" sm set-virtual-disk [true|false]");
System.err.println("");
System.err.println(" sm partition DISK [public|private|mixed] [ratio]");
System.err.println(" sm mount VOLUME");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fa1de03..147b5d0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1990,27 +1990,32 @@
}
}
- void dispatchMovedToDisplay(int displayId) {
+ void dispatchMovedToDisplay(int displayId, Configuration config) {
updateDisplay(displayId);
- onMovedToDisplay(displayId);
+ onMovedToDisplay(displayId, config);
}
/**
* Called by the system when the activity is moved from one display to another without
* recreation. This means that this activity is declared to handle all changes to configuration
* that happened when it was switched to another display, so it wasn't destroyed and created
- * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
- * applied configuration actually changed.
+ * again.
+ *
+ * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed. It is up to app developer to choose whether to handle
+ * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+ * call.
*
* <p>Use this callback to track changes to the displays if some activity functionality relies
* on an association with some display properties.
*
* @param displayId The id of the display to which activity was moved.
+ * @param config Configuration of the activity resources on new display after move.
*
* @see #onConfigurationChanged(Configuration)
- * @see View#onMovedToDisplay(int)
+ * @see View#onMovedToDisplay(int, Configuration)
*/
- public void onMovedToDisplay(int displayId) {
+ public void onMovedToDisplay(int displayId, Configuration config) {
}
/**
@@ -2735,6 +2740,10 @@
return true;
}
return false;
+ } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ // Don't consume TAB here since it's used for navigation. Arrow keys
+ // aren't considered "typing keys" so they already won't get consumed.
+ return false;
} else {
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
boolean clearSpannable = false;
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/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b4e6bd5..b5d1fa8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -808,11 +808,6 @@
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
- // TODO: Debugging added for bug:36406078 . Remove when done
- if (Log.isLoggable("36406078", Log.DEBUG)) {
- Log.d(TAG, "scheduleReceiver");
- }
-
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
sync, false, mAppThread.asBinder(), sendingUser);
@@ -899,11 +894,6 @@
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial) {
- // TODO: Debugging added for bug:36406078 . Remove when done
- if (Log.isLoggable("36406078", Log.DEBUG)) {
- Log.d(TAG, "bindApplication: " + processName);
- }
-
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
@@ -3239,10 +3229,6 @@
if (receiver.getPendingResult() != null) {
data.finish();
}
- // TODO: Debugging added for bug:36406078 . Remove when done
- if (Log.isLoggable("36406078", Log.DEBUG)) {
- Log.d(TAG, "handleReceiver done");
- }
}
// Instantiate a BackupAgent and tell it that it's alive
@@ -4815,16 +4801,18 @@
* {@link ActivityClientRecord#overrideConfig}.
* @param displayId The id of the display where the Activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+ * @return {@link Configuration} instance sent to client, null if not sent.
*/
- private void performConfigurationChangedForActivity(ActivityClientRecord r,
+ private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
- performActivityConfigurationChanged(r.activity, r.tmpConfig, r.overrideConfig, displayId,
- movedToDifferentDisplay);
+ final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+ r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+ return reportedConfig;
}
/**
@@ -4878,9 +4866,11 @@
* ActivityManager.
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+ * @return Configuration sent to client, null if no changes and not moved to different display.
*/
- private void performActivityConfigurationChanged(Activity activity, Configuration newConfig,
- Configuration amOverrideConfig, int displayId, boolean movedToDifferentDisplay) {
+ private Configuration performActivityConfigurationChanged(Activity activity,
+ Configuration newConfig, Configuration amOverrideConfig, int displayId,
+ boolean movedToDifferentDisplay) {
if (activity == null) {
throw new IllegalArgumentException("No activity provided.");
}
@@ -4911,7 +4901,7 @@
}
if (!shouldChangeConfig && !movedToDifferentDisplay) {
// Nothing significant, don't proceed with updating and reporting.
- return;
+ return null;
}
// Propagate the configuration change to ResourcesManager and Activity.
@@ -4934,22 +4924,22 @@
activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(newConfig);
+ // Apply the ContextThemeWrapper override if necessary.
+ // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+ // in many places.
+ final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
+ contextThemeWrapperOverrideConfig);
+
if (!REPORT_TO_ACTIVITY) {
// Not configured to report to activity.
- return;
+ return configToReport;
}
if (movedToDifferentDisplay) {
- activity.dispatchMovedToDisplay(displayId);
+ activity.dispatchMovedToDisplay(displayId, configToReport);
}
if (shouldChangeConfig) {
- // Apply the ContextThemeWrapper override if necessary.
- // NOTE: Make sure the configurations are not modified, as they are treated as immutable
- // in many places.
- final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
- newConfig, contextThemeWrapperOverrideConfig);
-
activity.mCalled = false;
activity.onConfigurationChanged(configToReport);
if (!activity.mCalled) {
@@ -4957,6 +4947,8 @@
" did not call through to super.onConfigurationChanged()");
}
}
+
+ return configToReport;
}
public final void applyConfigurationToResources(Configuration config) {
@@ -5129,10 +5121,10 @@
+ r.activityInfo.name + ", displayId=" + displayId
+ ", config=" + data.overrideConfig);
- performConfigurationChangedForActivity(r, mCompatConfiguration, displayId,
- true /* movedToDifferentDisplay */);
+ final Configuration reportedConfig = performConfigurationChangedForActivity(r,
+ mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
if (viewRoot != null) {
- viewRoot.onMovedToDisplay(displayId);
+ viewRoot.onMovedToDisplay(displayId, reportedConfig);
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
@@ -5142,7 +5134,7 @@
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
if (viewRoot != null) {
- viewRoot.updateConfiguration();
+ viewRoot.updateConfiguration(displayId);
}
mSomeActivitiesChanged = true;
}
@@ -5778,10 +5770,6 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- // TODO: Debugging added for bug:36406078 . Remove when done
- if (Log.isLoggable("36406078", Log.DEBUG)) {
- Log.d(TAG, "handleBindApplication done");
- }
}
/*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index ef2db4a..2062930 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -18,6 +18,7 @@
import android.os.Build;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.ArrayMap;
import com.android.internal.os.PathClassLoaderFactory;
import dalvik.system.PathClassLoader;
@@ -29,8 +30,16 @@
}
ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
- String librarySearchPath, String libraryPermittedPath,
- ClassLoader parent) {
+ String librarySearchPath, String libraryPermittedPath,
+ ClassLoader parent) {
+ // For normal usage the cache key used is the same as the zip path.
+ return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
+ libraryPermittedPath, parent, zip);
+ }
+
+ private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
+ String librarySearchPath, String libraryPermittedPath,
+ ClassLoader parent, String cacheKey) {
/*
* This is the parent we use if they pass "null" in. In theory
* this should be the "system" class loader; in practice we
@@ -50,7 +59,7 @@
* new ClassLoader for the zip archive.
*/
if (parent == baseParent) {
- ClassLoader loader = mLoaders.get(zip);
+ ClassLoader loader = mLoaders.get(cacheKey);
if (loader != null) {
return loader;
}
@@ -71,7 +80,7 @@
setupVulkanLayerPath(pathClassloader, librarySearchPath);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- mLoaders.put(zip, pathClassloader);
+ mLoaders.put(cacheKey, pathClassloader);
return pathClassloader;
}
@@ -87,12 +96,16 @@
* by this class. This is used in the WebView zygote, where its presence in the cache speeds up
* startup and enables memory sharing.
*/
- public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath) {
- // The correct paths are calculated by WebViewZygote in the system server and passed to
- // us here. We hardcode the other parameters: WebView always targets the current SDK,
- // does not need to use non-public system libraries, and uses the base classloader as its
- // parent to permit usage of the cache.
- return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null);
+ public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath,
+ String cacheKey) {
+ // The correct paths are calculated by WebViewZygote in the system server and passed to
+ // us here. We hardcode the other parameters: WebView always targets the current SDK,
+ // does not need to use non-public system libraries, and uses the base classloader as its
+ // parent to permit usage of the cache.
+ // The cache key is passed separately to enable the stub WebView to be cached under the
+ // stub's APK path, when the actual package path is the donor APK.
+ return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
+ cacheKey);
}
private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 286f8570..461f9cc 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2630,4 +2630,13 @@
throw e.rethrowAsRuntimeException();
}
}
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ try {
+ return mPM.getInstantAppResolverSettingsComponent();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 82229d5..b9d1d91 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -629,6 +629,11 @@
*/
void setDisablePreviewScreenshots(IBinder token, boolean disable);
+ /**
+ * Return the user id of last resumed activity.
+ */
+ int getLastResumedActivityUserId();
+
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
// If a transaction which will also be used on the native side is being inserted, add it
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 124749a..f719749 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -93,6 +93,7 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Build;
+import android.os.Debug;
import android.os.DropBoxManager;
import android.os.HardwarePropertiesManager;
import android.os.IBatteryPropertiesRegistrar;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 9e2eb84..5d1e8ec 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
@@ -778,7 +778,7 @@
/**
* Set the preferred connection PHY for this app. Please note that this is just a
- * recommendation, wether the PHY change will happen depends on other applications peferences,
+ * recommendation, whether the PHY change will happen depends on other applications peferences,
* local and remote controller capabilities. Controller can override these settings.
* <p>
* {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index c991e2f..2df2ed8 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
@@ -550,7 +550,7 @@
/**
* Set the preferred connection PHY for this app. Please note that this is just a
- * recommendation, wether the PHY change will happen depends on other applications peferences,
+ * recommendation, whether the PHY change will happen depends on other applications peferences,
* local and remote controller capabilities. Controller can override these settings.
* <p>
* {@link BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 652a1c60..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/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index fe1f425..d36c0d6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -279,7 +279,7 @@
* When set to true, advertising set will advertise 4.x Spec compliant
* advertisements.
*
- * @param isLegacy wether legacy advertising mode should be used.
+ * @param isLegacy whether legacy advertising mode should be used.
*/
public Builder setLegacyMode(boolean isLegacy) {
this.isLegacy = isLegacy;
@@ -287,12 +287,12 @@
}
/**
- * Set wether advertiser address should be ommited from all packets. If this
+ * Set whether advertiser address should be ommited from all packets. If this
* mode is used, periodic advertising can't be enabled for this set.
*
* This is used only if legacy mode is not used.
*
- * @param isAnonymous wether anonymous advertising should be used.
+ * @param isAnonymous whether anonymous advertising should be used.
*/
public Builder setAnonymous(boolean isAnonymous) {
this.isAnonymous = isAnonymous;
@@ -300,11 +300,11 @@
}
/**
- * Set wether TX power should be included in the extended header.
+ * Set whether TX power should be included in the extended header.
*
* This is used only if legacy mode is not used.
*
- * @param includeTxPower wether TX power should be included in extended
+ * @param includeTxPower whether TX power should be included in extended
* header
*/
public Builder setIncludeTxPower(boolean includeTxPower) {
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index ebc92bd..149540c 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -93,7 +93,7 @@
private int interval = INTERVAL_MAX;
/**
- * Set wether the Periodic Advertising should be enabled for this set.
+ * Set whether the Periodic Advertising should be enabled for this set.
*/
public Builder setEnable(boolean enable) {
this.enable = enable;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bb844a3..919f4ba 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* A request for the user to select a companion device to associate with.
@@ -69,6 +70,20 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AssociationRequest that = (AssociationRequest) o;
+ return mSingleDevice == that.mSingleDevice &&
+ Objects.equals(mDeviceFilters, that.mDeviceFilters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSingleDevice, mDeviceFilters);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (mSingleDevice ? 1 : 0));
dest.writeParcelableList(mDeviceFilters, flags);
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 1d8df7f..84e1536 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -123,6 +124,22 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BluetoothDeviceFilter that = (BluetoothDeviceFilter) o;
+ return Objects.equals(mNamePattern, that.mNamePattern) &&
+ Objects.equals(mAddress, that.mAddress) &&
+ Objects.equals(mServiceUuids, that.mServiceUuids) &&
+ Objects.equals(mServiceUuidMasks, that.mServiceUuidMasks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern, mAddress, mServiceUuids, mServiceUuidMasks);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 8a316f1..3665d1b 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -37,7 +37,7 @@
private BluetoothDeviceFilterUtils() {}
private static final boolean DEBUG = false;
- private static final String LOG_TAG = "BluetoothDeviceFilterUtil";
+ private static final String LOG_TAG = "BluetoothDeviceFilterUtils";
@Nullable
static String patternToString(@Nullable Pattern p) {
@@ -50,8 +50,10 @@
}
static boolean matches(ScanFilter filter, BluetoothDevice device) {
- return matchesAddress(filter.getDeviceAddress(), device)
+ boolean result = matchesAddress(filter.getDeviceAddress(), device)
&& matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device);
+ if (DEBUG) debugLogMatchResult(result, device, filter);
+ return result;
}
static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index e057fbc..5a1b29d 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -31,11 +31,14 @@
import android.os.Parcel;
import android.provider.OneTimeUseBuilder;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.util.BitUtils;
import com.android.internal.util.ObjectUtils;
import com.android.internal.util.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -45,6 +48,9 @@
*/
public final class BluetoothLEDeviceFilter implements DeviceFilter<ScanResult> {
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "BluetoothLEDeviceFilter";
+
private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
private final Pattern mNamePattern;
@@ -143,9 +149,13 @@
/** @hide */
@Override
public boolean matches(ScanResult device) {
- return matches(device.getDevice())
- && BitUtils.maskedEquals(device.getScanRecord().getBytes(),
- mRawDataFilter, mRawDataFilterMask);
+ boolean result = matches(device.getDevice())
+ && (mRawDataFilter == null
+ || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
+ mRawDataFilter, mRawDataFilterMask));
+ if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
+ ") -> " + result);
+ return result;
}
private boolean matches(BluetoothDevice device) {
@@ -160,9 +170,39 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
+ return mRenameBytesFrom == that.mRenameBytesFrom &&
+ mRenameBytesTo == that.mRenameBytesTo &&
+ mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
+ Objects.equals(mNamePattern, that.mNamePattern) &&
+ Objects.equals(mScanFilter, that.mScanFilter) &&
+ Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
+ Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
+ Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
+ Objects.equals(mRenameSuffix, that.mRenameSuffix);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
+ mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
+ mRenameBytesReverseOrder);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(patternToString(getNamePattern()));
dest.writeParcelable(mScanFilter, flags);
+ dest.writeByteArray(mRawDataFilter);
+ dest.writeByteArray(mRawDataFilterMask);
+ dest.writeString(mRenamePrefix);
+ dest.writeString(mRenameSuffix);
+ dest.writeInt(mRenameBytesFrom);
+ dest.writeInt(mRenameBytesTo);
+ dest.writeBoolean(mRenameBytesReverseOrder);
}
@Override
@@ -174,13 +214,23 @@
= new Creator<BluetoothLEDeviceFilter>() {
@Override
public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
- return new BluetoothLEDeviceFilter.Builder()
+ Builder builder = new Builder()
.setNamePattern(patternFromString(in.readString()))
- .setScanFilter(in.readParcelable(null))
- .setRawDataFilter(in.readBlob(), in.readBlob())
- .setRename(in.readString(), in.readString(),
- in.readInt(), in.readInt(), in.readBoolean())
- .build();
+ .setScanFilter(in.readParcelable(null));
+ byte[] rawDataFilter = in.createByteArray();
+ byte[] rawDataFilterMask = in.createByteArray();
+ if (rawDataFilter != null) {
+ builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
+ }
+ String renamePrefix = in.readString();
+ String suffix = in.readString();
+ int bytesFrom = in.readInt();
+ int bytesTo = in.readInt();
+ boolean bytesReverseOrder = in.readBoolean();
+ if (renamePrefix != null) {
+ builder.setRename(renamePrefix, suffix, bytesFrom, bytesTo, bytesReverseOrder);
+ }
+ return builder.build();
}
@Override
@@ -240,12 +290,14 @@
*/
@NonNull
public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
- @NonNull byte[] rawDataFilterMask) {
+ @Nullable byte[] rawDataFilterMask) {
checkNotUsed();
- checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+ Preconditions.checkNotNull(rawDataFilter);
+ checkArgument(rawDataFilterMask == null ||
+ rawDataFilter.length == rawDataFilterMask.length,
"Mask and filter should be the same length");
- mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
- mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+ mRawDataFilter = rawDataFilter;
+ mRawDataFilterMask = rawDataFilterMask;
return this;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 78e3de4..7b38863 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -17,6 +17,8 @@
package android.companion;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -24,7 +26,6 @@
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -43,7 +44,7 @@
*/
public final class CompanionDeviceManager {
- private static final boolean DEBUG = false; //TODO
+ private static final boolean DEBUG = false;
private static final String LOG_TAG = "CompanionDeviceManager";
/**
@@ -129,10 +130,9 @@
if (!checkFeaturePresent()) {
return;
}
-
- final Handler finalHandler = handler != null
- ? handler
- : new Handler(Looper.getMainLooper());
+ checkNotNull(request, "Request cannot be null");
+ checkNotNull(callback, "Callback cannot be null");
+ final Handler finalHandler = Handler.mainIfNull(handler);
try {
mService.associate(
request,
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
index 1ab9ce1..b6e704c 100644
--- a/core/java/android/companion/WifiDeviceFilter.java
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -29,6 +29,7 @@
import android.os.Parcel;
import android.provider.OneTimeUseBuilder;
+import java.util.Objects;
import java.util.regex.Pattern;
/**
@@ -75,6 +76,19 @@
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ WifiDeviceFilter that = (WifiDeviceFilter) o;
+ return Objects.equals(mNamePattern, that.mNamePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNamePattern);
+ }
+
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(patternToString(getNamePattern()));
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index c28172c..f1c2f34 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -150,7 +150,7 @@
public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
synchronized (mPrimaryClipChangedListeners) {
- if (mPrimaryClipChangedListeners.size() == 0) {
+ if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.addPrimaryClipChangedListener(
mPrimaryClipChangedServiceListener, mContext.getOpPackageName());
@@ -165,7 +165,7 @@
public void removePrimaryClipChangedListener(OnPrimaryClipChangedListener what) {
synchronized (mPrimaryClipChangedListeners) {
mPrimaryClipChangedListeners.remove(what);
- if (mPrimaryClipChangedListeners.size() == 0) {
+ if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.removePrimaryClipChangedListener(
mPrimaryClipChangedServiceListener);
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index fd1e24a..8f3a317 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -508,14 +508,14 @@
/** Create a ContentProviderOperation from this {@link Builder}. */
public ContentProviderOperation build() {
if (mType == TYPE_UPDATE) {
- if ((mValues == null || mValues.size() == 0)
- && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)) {
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())) {
throw new IllegalArgumentException("Empty values");
}
}
if (mType == TYPE_ASSERT) {
- if ((mValues == null || mValues.size() == 0)
- && (mValuesBackReferences == null || mValuesBackReferences.size() == 0)
+ if ((mValues == null || mValues.isEmpty())
+ && (mValuesBackReferences == null || mValuesBackReferences.isEmpty())
&& (mExpectedCount == null)) {
throw new IllegalArgumentException("Empty values");
}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 3a87cb3..6f93798 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -204,6 +204,17 @@
}
/**
+ * Indicates whether this collection is empty.
+ *
+ * @return true iff size == 0
+ * {@hide}
+ * TODO: consider exposing this new method publicly
+ */
+ public boolean isEmpty() {
+ return mValues.isEmpty();
+ }
+
+ /**
* Remove a single value.
*
* @param key the name of the value to remove
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fb86791..7890a96 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1532,6 +1532,19 @@
= "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
/**
+ * Activity Action: Launch ephemeral settings.
+ *
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS
+ = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS";
+
+ /**
* Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
* package. Specifies the installer package name; this package will receive the
* {@link #ACTION_APP_ERROR} intent.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 59b022d..147df76 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -630,4 +630,6 @@
boolean canRequestPackageInstalls(String packageName, int userId);
void deletePreloadsFileCache();
+
+ ComponentName getInstantAppResolverSettingsComponent();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 71db5d3..136c13b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -153,6 +153,7 @@
MATCH_UNINSTALLED_PACKAGES,
MATCH_SYSTEM_ONLY,
MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_DISABLED_COMPONENTS,
MATCH_DISABLED_UNTIL_USED_COMPONENTS,
MATCH_INSTANT,
GET_DISABLED_UNTIL_USED_COMPONENTS,
@@ -431,6 +432,7 @@
* This will not return information on any unbundled update to system components.
* @hide
*/
+ @SystemApi
public static final int MATCH_FACTORY_ONLY = 0x00200000;
/**
@@ -3730,6 +3732,7 @@
*
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {@link #MATCH_DISABLED_COMPONENTS}, {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}
* {@link #MATCH_SYSTEM_ONLY}, {@link #MATCH_UNINSTALLED_PACKAGES}
* to modify the data returned.
*
@@ -3743,6 +3746,7 @@
*
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
+ * @see #MATCH_DISABLED_COMPONENTS
* @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
* @see #MATCH_SYSTEM_ONLY
* @see #MATCH_UNINSTALLED_PACKAGES
@@ -3757,6 +3761,7 @@
*
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {@link #MATCH_DISABLED_COMPONENTS}, {@link #MATCH_DISABLED_UNTIL_USED_COMPONENTS}
* {@link #MATCH_SYSTEM_ONLY}, {@link #MATCH_UNINSTALLED_PACKAGES}
* to modify the data returned.
* @param userId The user for whom the installed applications are to be listed
@@ -3772,6 +3777,7 @@
*
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
+ * @see #MATCH_DISABLED_COMPONENTS
* @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
* @see #MATCH_SYSTEM_ONLY
* @see #MATCH_UNINSTALLED_PACKAGES
@@ -6233,4 +6239,14 @@
* @see {@link android.provider.Settings#ACTION_MANAGE_EXTERNAL_SOURCES}
*/
public abstract boolean canRequestPackageInstalls();
+
+ /**
+ * Return the {@link ComponentName} of the activity providing Settings for the Instant App
+ * resolver.
+ *
+ * @see {@link android.content.intent#ACTION_EPHEMERAL_RESOLVER_SETTINGS}
+ * @hide
+ */
+ @SystemApi
+ public abstract ComponentName getInstantAppResolverSettingsComponent();
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 6272822..370af17 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -172,6 +172,7 @@
* @param packageName The package name.
* @param userId The user for which to check.
* @return Whether was launched.
+ * @throws IllegalArgumentException if the package is not found
*/
public abstract boolean wasPackageEverLaunched(String packageName, int userId);
@@ -241,6 +242,7 @@
public abstract void grantEphemeralAccess(int userId, Intent intent,
int targetAppId, int ephemeralAppId);
+ public abstract boolean isInstantAppInstallerComponent(ComponentName component);
/**
* Prunes instant apps and state associated with uninstalled
* instant apps according to the current platform policy.
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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8e17832..fe849b8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1449,7 +1449,7 @@
sql.append('(');
Object[] bindArgs = null;
- int size = (initialValues != null && initialValues.size() > 0)
+ int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
@@ -1541,7 +1541,7 @@
*/
public int updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
- if (values == null || values.size() == 0) {
+ if (values == null || values.isEmpty()) {
throw new IllegalArgumentException("Empty values");
}
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/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dbe1394..00e047d 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -1112,6 +1112,8 @@
return ImageFormat.DEPTH_POINT_CLOUD;
case HAL_PIXEL_FORMAT_Y16:
return ImageFormat.DEPTH16;
+ case HAL_PIXEL_FORMAT_RAW16:
+ return ImageFormat.RAW_DEPTH;
case ImageFormat.JPEG:
throw new IllegalArgumentException(
"ImageFormat.JPEG is an unknown internal format");
@@ -1179,6 +1181,8 @@
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
+ case ImageFormat.RAW_DEPTH:
+ return HAL_PIXEL_FORMAT_RAW16;
default:
return format;
}
@@ -1220,6 +1224,7 @@
return HAL_DATASPACE_V0_JFIF;
case ImageFormat.DEPTH_POINT_CLOUD:
case ImageFormat.DEPTH16:
+ case ImageFormat.RAW_DEPTH:
return HAL_DATASPACE_DEPTH;
default:
return HAL_DATASPACE_UNKNOWN;
@@ -1609,6 +1614,8 @@
return "DEPTH16";
case ImageFormat.DEPTH_POINT_CLOUD:
return "DEPTH_POINT_CLOUD";
+ case ImageFormat.RAW_DEPTH:
+ return "RAW_DEPTH";
case ImageFormat.PRIVATE:
return "PRIVATE";
default:
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 14bb923..58c3d24 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -196,6 +196,19 @@
return mIsCaptureSupported;
}
+ /**
+ * {@code true} if the module supports background scanning. At the given time it may not
+ * be available though, see {@link RadioTuner#startBackgroundScan()}.
+ *
+ * @return {@code true} if background scanning is supported (not necessary available
+ * at a given time), {@code false} otherwise.
+ *
+ * @hide FutureFeature
+ */
+ public boolean isBackgroundScanningSupported() {
+ return false;
+ }
+
/** List of descriptors for all bands supported by this module.
* @return an array of {@link BandDescriptor}.
*/
diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java
index 8964893..033403a 100644
--- a/core/java/android/hardware/radio/RadioModule.java
+++ b/core/java/android/hardware/radio/RadioModule.java
@@ -79,6 +79,8 @@
public native int getProgramInformation(RadioManager.ProgramInfo[] info);
+ public native boolean startBackgroundScan();
+
public native @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter);
public native boolean isAntennaConnected();
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index c8034eb..1159d7d 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -212,6 +212,23 @@
public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
/**
+ * Initiates a background scan to update internally cached program list.
+ *
+ * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+ *
+ * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+ * be called if the return value of this call was {@code true}. As result of this call
+ * programListChanged may be triggered (if the scanned list differs).
+ *
+ * @return {@code true} if the scan was properly scheduled, {@code false} if the scan feature
+ * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
+ * or permanently if the feature is not supported
+ * (see ModuleProperties#isBackgroundScanningSupported()).
+ * @hide FutureFeature
+ */
+ public abstract boolean startBackgroundScan();
+
+ /**
* Get the list of discovered radio stations.
*
* To get the full list, set filter to null or empty string. Otherwise, client application
@@ -219,7 +236,8 @@
*
* @param filter vendor-specific selector for radio stations.
* @return a list of radio stations.
- * @throws IllegalStateException if the scan is in progress or has not been started.
+ * @throws IllegalStateException if the scan is in progress or has not been started,
+ * startBackgroundScan() call may fix it.
* @throws IllegalArgumentException if the filter argument is not valid.
* @hide FutureFeature
*/
@@ -317,6 +335,32 @@
* with control set to {@code true}.
*/
public void onControlChanged(boolean control) {}
+
+ /**
+ * onBackgroundScanAvailabilityChange() is called when background scan
+ * feature becomes available or not.
+ *
+ * @param isAvailable true, if the tuner turned temporarily background-
+ * capable, false in the other case.
+ * @hide FutureFeature
+ */
+ public void onBackgroundScanAvailabilityChange(boolean isAvailable) {}
+
+ /**
+ * Called when a background scan completes successfully.
+ *
+ * @hide FutureFeature
+ */
+ public void onBackgroundScanComplete() {}
+
+ /**
+ * Called when available program list changed.
+ *
+ * Use getProgramList() to get the actual list.
+ *
+ * @hide FutureFeature
+ */
+ public void onProgramListChanged() {}
}
}
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 60b27b4..0448221 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -16,6 +16,7 @@
package android.metrics;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.util.Log;
import android.util.SparseArray;
@@ -118,6 +119,16 @@
return this;
}
+ /**
+ * @param component to replace the existing setting.
+ * @hide
+ */
+ public LogMaker setComponentName(ComponentName component) {
+ entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, component.getPackageName());
+ entries.put(MetricsEvent.FIELD_CLASS_NAME, component.getClassName());
+ return this;
+ }
+
/** Remove the package name property. */
public LogMaker clearPackageName() {
entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME);
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/os/Handler.java b/core/java/android/os/Handler.java
index 3c7c962..8678d95 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.Printer;
@@ -69,6 +71,7 @@
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
+ private static Handler MAIN_THREAD_HANDLER = null;
/**
* Callback interface you can use when instantiating a Handler to avoid
@@ -231,6 +234,21 @@
mAsynchronous = async;
}
+ /** @hide */
+ @NonNull
+ public static Handler getMain() {
+ if (MAIN_THREAD_HANDLER == null) {
+ MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
+ }
+ return MAIN_THREAD_HANDLER;
+ }
+
+ /** @hide */
+ @NonNull
+ public static Handler mainIfNull(@Nullable Handler handler) {
+ return handler == null ? getMain() : handler;
+ }
+
/** {@hide} */
public String getTraceName(Message message) {
final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
new file mode 100644
index 0000000..7dec4d7
--- /dev/null
+++ b/core/java/android/os/HidlSupport.java
@@ -0,0 +1,159 @@
+/*
+ * 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.os;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/** @hide */
+public class HidlSupport {
+ /**
+ * Similar to Objects.deepEquals, but also take care of lists.
+ * Two objects of HIDL types are considered equal if:
+ * 1. Both null
+ * 2. Both non-null, and of the same class, and:
+ * 2.1 Both are primitive arrays / enum arrays, elements are equal using == check
+ * 2.2 Both are object arrays, elements are checked recursively
+ * 2.3 Both are Lists, elements are checked recursively
+ * 2.4 (If both are collections other than lists or maps, throw an error)
+ * 2.5 lft.equals(rgt) returns true
+ */
+ public static boolean deepEquals(Object lft, Object rgt) {
+ if (lft == rgt) {
+ return true;
+ }
+ if (lft == null || rgt == null) {
+ return false;
+ }
+
+ Class<?> lftClazz = lft.getClass();
+ Class<?> rgtClazz = rgt.getClass();
+ if (lftClazz != rgtClazz) {
+ return false;
+ }
+
+ if (lftClazz.isArray()) {
+ Class<?> lftElementType = lftClazz.getComponentType();
+ if (lftElementType != rgtClazz.getComponentType()) {
+ return false;
+ }
+
+ if (lftElementType.isPrimitive()) {
+ return Objects.deepEquals(lft, rgt);
+ }
+
+ Object[] lftArray = (Object[])lft;
+ Object[] rgtArray = (Object[])rgt;
+ return (lftArray.length == rgtArray.length) &&
+ IntStream.range(0, lftArray.length).allMatch(
+ i -> deepEquals(lftArray[i], rgtArray[i]));
+ }
+
+ if (lft instanceof List<?>) {
+ List<Object> lftList = (List<Object>)lft;
+ List<Object> rgtList = (List<Object>)rgt;
+ if (lftList.size() != rgtList.size()) {
+ return false;
+ }
+
+ Iterator<Object> lftIter = lftList.iterator();
+ return rgtList.stream()
+ .allMatch(rgtElement -> deepEquals(lftIter.next(), rgtElement));
+ }
+
+ throwErrorIfUnsupportedType(lft);
+
+ return lft.equals(rgt);
+ }
+
+ /**
+ * Similar to Arrays.deepHashCode, but also take care of lists.
+ */
+ public static int deepHashCode(Object o) {
+ if (o == null) {
+ return 0;
+ }
+ Class<?> clazz = o.getClass();
+ if (clazz.isArray()) {
+ Class<?> elementType = clazz.getComponentType();
+ if (elementType.isPrimitive()) {
+ return primitiveArrayHashCode(o);
+ }
+ return Arrays.hashCode(Arrays.stream((Object[])o)
+ .mapToInt(element -> deepHashCode(element))
+ .toArray());
+ }
+
+ if (o instanceof List<?>) {
+ return Arrays.hashCode(((List<Object>)o).stream()
+ .mapToInt(element -> deepHashCode(element))
+ .toArray());
+ }
+
+ throwErrorIfUnsupportedType(o);
+
+ return o.hashCode();
+ }
+
+ private static void throwErrorIfUnsupportedType(Object o) {
+ if (o instanceof Collection<?> && !(o instanceof List<?>)) {
+ throw new UnsupportedOperationException(
+ "Cannot check equality on collections other than lists: " +
+ o.getClass().getName());
+ }
+
+ if (o instanceof Map<?, ?>) {
+ throw new UnsupportedOperationException(
+ "Cannot check equality on maps");
+ }
+ }
+
+ private static int primitiveArrayHashCode(Object o) {
+ Class<?> elementType = o.getClass().getComponentType();
+ if (elementType == boolean.class) {
+ return Arrays.hashCode(((boolean[])o));
+ }
+ if (elementType == byte.class) {
+ return Arrays.hashCode(((byte[])o));
+ }
+ if (elementType == char.class) {
+ return Arrays.hashCode(((char[])o));
+ }
+ if (elementType == double.class) {
+ return Arrays.hashCode(((double[])o));
+ }
+ if (elementType == float.class) {
+ return Arrays.hashCode(((float[])o));
+ }
+ if (elementType == int.class) {
+ return Arrays.hashCode(((int[])o));
+ }
+ if (elementType == long.class) {
+ return Arrays.hashCode(((long[])o));
+ }
+ if (elementType == short.class) {
+ return Arrays.hashCode(((short[])o));
+ }
+ // Should not reach here.
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 63d3e7a..44dbcfb 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -21,6 +21,7 @@
import android.os.LooperProto;
import android.util.Log;
import android.util.Printer;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
/**
@@ -76,6 +77,9 @@
private Printer mLogging;
private long mTraceTag;
+ /* If set, the looper will show a warning log if a message dispatch takes longer than time. */
+ private long mSlowDispatchThresholdMs;
+
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
@@ -148,17 +152,30 @@
msg.callback + ": " + msg.what);
}
+ final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
+
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
+ final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
+ final long end;
try {
msg.target.dispatchMessage(msg);
+ end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
+ if (slowDispatchThresholdMs > 0) {
+ final long time = end - start;
+ if (time > slowDispatchThresholdMs) {
+ Slog.w(TAG, "Dispatch took " + time + "ms on "
+ + Thread.currentThread().getName() + ", h=" +
+ msg.target + " cb=" + msg.callback + " msg=" + msg.what);
+ }
+ }
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
@@ -226,6 +243,11 @@
mTraceTag = traceTag;
}
+ /** {@hide} */
+ public void setSlowDispatchThresholdMs(long slowDispatchThresholdMs) {
+ mSlowDispatchThresholdMs = slowDispatchThresholdMs;
+ }
+
/**
* Quits the looper.
* <p>
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2e35a51..76128e6 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2039,14 +2039,20 @@
}
}
+ /** @deprecated use {@link android.system.Os#open(String, int, int)} */
+ @Deprecated
+ static native FileDescriptor openFileDescriptor(String file, int mode)
+ throws FileNotFoundException;
- /*package*/ static native FileDescriptor openFileDescriptor(String file,
- int mode) throws FileNotFoundException;
- /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
- throws IOException;
- /*package*/ static native void closeFileDescriptor(FileDescriptor desc)
- throws IOException;
- /*package*/ static native void clearFileDescriptor(FileDescriptor desc);
+ /** @deprecated use {@link android.system.Os#dup(FileDescriptor)} */
+ @Deprecated
+ static native FileDescriptor dupFileDescriptor(FileDescriptor orig) throws IOException;
+
+ /** @deprecated use {@link android.system.Os#close(FileDescriptor)} */
+ @Deprecated
+ static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
+
+ static native void clearFileDescriptor(FileDescriptor desc);
/**
* Read a byte value from the parcel at the current dataPosition().
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 8882672..3212139 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -17,11 +17,21 @@
package android.os;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
import static android.system.OsConstants.SEEK_SET;
-import static android.system.OsConstants.SOCK_STREAM;
import static android.system.OsConstants.SOCK_SEQPACKET;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.S_IROTH;
+import static android.system.OsConstants.S_IRWXG;
+import static android.system.OsConstants.S_IRWXU;
import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_IWOTH;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
@@ -33,6 +43,7 @@
import android.util.Log;
import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
import libcore.io.Memory;
@@ -279,8 +290,28 @@
"Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
}
+ int flags = 0;
+ switch (mode & MODE_READ_WRITE) {
+ case 0:
+ case MODE_READ_ONLY: flags = O_RDONLY; break;
+ case MODE_WRITE_ONLY: flags = O_WRONLY; break;
+ case MODE_READ_WRITE: flags = O_RDWR; break;
+ }
+
+ if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
+ if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
+ if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+
+ int realMode = S_IRWXU | S_IRWXG;
+ if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
+ if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
+
final String path = file.getPath();
- return Parcel.openFileDescriptor(path, mode);
+ try {
+ return Os.open(path, flags, realMode);
+ } catch (ErrnoException e) {
+ throw new FileNotFoundException(e.getMessage());
+ }
}
/**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index ef5bc5c..5fa2461 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -483,7 +483,7 @@
}
final String filenameArg = "--update_package=" + filename + "\n";
- final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+ final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
@@ -531,7 +531,7 @@
}
final String filenameArg = "--update_package=" + filename + "\n";
- final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
+ final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
@@ -647,7 +647,7 @@
reasonArg = "--reason=" + sanitizeArg(reason);
}
- final String localeArg = "--locale=" + Locale.getDefault().toString();
+ final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
@@ -678,7 +678,7 @@
reasonArg = "--reason=" + sanitizeArg(reason);
}
- final String localeArg = "--locale=" + Locale.getDefault().toString();
+ final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, "--wipe_cache", reasonArg, localeArg);
}
@@ -703,7 +703,7 @@
final String filename = packageFile.getCanonicalPath();
final String filenameArg = "--wipe_package=" + filename;
- final String localeArg = "--locale=" + Locale.getDefault().toString();
+ final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index b3366d8..8208438 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -487,11 +487,11 @@
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
- public void preloadPackageForAbi(String packagePath, String libsPath, String abi)
- throws ZygoteStartFailedEx, IOException {
+ public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+ String abi) throws ZygoteStartFailedEx, IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
- state.writer.write("3");
+ state.writer.write("4");
state.writer.newLine();
state.writer.write("--preload-package");
@@ -503,6 +503,9 @@
state.writer.write(libsPath);
state.writer.newLine();
+ state.writer.write(cacheKey);
+ state.writer.newLine();
+
state.writer.flush();
}
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 53c9a23..e5d73e0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -59,6 +59,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop;
+import com.android.internal.os.FuseAppLoop.UnmountedException;
+import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -82,6 +84,7 @@
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
+import libcore.io.IoUtils;
/**
* StorageManager is the interface to the systems storage service. The storage
@@ -113,6 +116,8 @@
public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
/** {@hide} */
public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
+ /** {@hide} */
+ public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
@@ -140,6 +145,8 @@
public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 2;
/** {@hide} */
public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 3;
+ /** {@hide} */
+ public static final int DEBUG_VIRTUAL_DISK = 1 << 4;
// NOTE: keep in sync with installd
/** {@hide} */
@@ -1386,53 +1393,52 @@
/** {@hide} */
@VisibleForTesting
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
- int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
+ int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
throws IOException {
MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
// Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
// invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
// the bridge by calling mountProxyFileDescriptorBridge.
- int retry = 3;
- while (retry-- > 0) {
+ while (true) {
try {
synchronized (mFuseAppLoopLock) {
+ boolean newlyCreated = false;
if (mFuseAppLoop == null) {
final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
if (mount == null) {
- Log.e(TAG, "Failed to open proxy file bridge.");
- throw new IOException("Failed to open proxy file bridge.");
+ throw new IOException("Failed to mount proxy bridge");
}
- mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
+ mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory);
+ newlyCreated = true;
}
-
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
try {
- final int fileId = mFuseAppLoop.registerCallback(callback);
- final ParcelFileDescriptor pfd =
- mStorageManager.openProxyFileDescriptor(
- mFuseAppLoop.getMountPointId(), fileId, mode);
- if (pfd != null) {
- return pfd;
+ final int fileId = mFuseAppLoop.registerCallback(callback, handler);
+ final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
+ mFuseAppLoop.getMountPointId(), fileId, mode);
+ if (pfd == null) {
+ mFuseAppLoop.unregisterCallback(fileId);
+ throw new FuseUnavailableMountException(
+ mFuseAppLoop.getMountPointId());
}
- // Probably the bridge is being unmounted but mFuseAppLoop has not been
- // noticed it yet.
- mFuseAppLoop.unregisterCallback(fileId);
- } catch (FuseAppLoop.UnmountedException error) {
- Log.d(TAG, "mFuseAppLoop has been already unmounted.");
+ return pfd;
+ } catch (FuseUnavailableMountException exception) {
+ // The bridge is being unmounted. Tried to recreate it unless the bridge was
+ // just created.
+ if (newlyCreated) {
+ throw new IOException(exception);
+ }
mFuseAppLoop = null;
continue;
}
}
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- break;
- }
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ // Cannot recover from remote exception.
+ throw new IOException(e);
}
}
-
- throw new IOException("Failed to mount bridge.");
}
/**
@@ -1444,16 +1450,37 @@
* {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
* {@link ParcelFileDescriptor#MODE_READ_WRITE}
* @param callback Callback to process file operation requests issued on returned file
- * descriptor. The callback is invoked on a thread managed by the framework.
+ * descriptor.
* @return Seekable ParcelFileDescriptor.
* @throws IOException
*/
public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
int mode, ProxyFileDescriptorCallback callback)
throws IOException {
- return openProxyFileDescriptor(mode, callback, null);
+ return openProxyFileDescriptor(mode, callback, null, null);
}
+ /**
+ * Opens seekable ParcelFileDescriptor that routes file operation requests to
+ * ProxyFileDescriptorCallback.
+ *
+ * @param mode The desired access mode, must be one of
+ * {@link ParcelFileDescriptor#MODE_READ_ONLY},
+ * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+ * {@link ParcelFileDescriptor#MODE_READ_WRITE}
+ * @param callback Callback to process file operation requests issued on returned file
+ * descriptor.
+ * @param handler Handler that invokes callback methods.
+ * @return Seekable ParcelFileDescriptor.
+ * @throws IOException
+ */
+ public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+ int mode, ProxyFileDescriptorCallback callback, Handler handler)
+ throws IOException {
+ return openProxyFileDescriptor(mode, callback, handler, null);
+ }
+
+
/** {@hide} */
@VisibleForTesting
public int getProxyFileDescriptorMountPointId() {
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/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 2116847..8e01030 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -433,6 +433,7 @@
public static final void requestListeningState(Context context, ComponentName component) {
Intent intent = new Intent(ACTION_REQUEST_LISTENING);
intent.putExtra(EXTRA_COMPONENT, component);
+ intent.setPackage("com.android.systemui");
context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
}
}
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/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 70f9bdd..14d3ad7 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -22,13 +22,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
+import android.net.Uri;
import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import java.io.IOException;
import java.lang.annotation.Retention;
-import java.util.Arrays;
/**
@@ -44,20 +42,6 @@
}
/**
- * For duplicating file descriptors.
- *
- * Note that this copy constructor can not be usable for deep copy.
- * @hide
- */
- public FontConfig(@NonNull FontConfig config) {
- mFamilies = new Family[config.mFamilies.length];
- for (int i = 0; i < config.mFamilies.length; ++i) {
- mFamilies[i] = new Family(config.mFamilies[i]);
- }
- mAliases = Arrays.copyOf(config.mAliases, config.mAliases.length);
- }
-
- /**
* Returns the ordered list of families included in the system fonts.
*/
public @NonNull Family[] getFamilies() {
@@ -174,7 +158,7 @@
private final @NonNull Axis[] mAxes;
private final int mWeight;
private final boolean mIsItalic;
- private @Nullable ParcelFileDescriptor mFd;
+ private Uri mUri;
/**
* @hide
@@ -186,29 +170,6 @@
mAxes = axes;
mWeight = weight;
mIsItalic = isItalic;
- mFd = null;
- }
-
- /**
- * This is for duplicating FileDescriptors.
- *
- * Note that this copy ctor doesn't deep copy the members.
- *
- * @hide
- */
- public Font(Font origin) {
- mFontName = origin.mFontName;
- mTtcIndex = origin.mTtcIndex;
- mAxes = origin.mAxes;
- mWeight = origin.mWeight;
- mIsItalic = origin.mIsItalic;
- if (origin.mFd != null) {
- try {
- mFd = origin.mFd.dup();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
}
/**
@@ -247,17 +208,20 @@
}
/**
- * Returns a file descriptor to access the specified font. This should be closed after use.
+ * Returns the content uri associated to this font.
+ *
+ * You can reach to the font contents by calling {@link
+ * android.content.ContentResolver#openInputStream}.
*/
- public @Nullable ParcelFileDescriptor getFd() {
- return mFd;
+ public @Nullable Uri getUri() {
+ return mUri;
}
/**
* @hide
*/
- public void setFd(@NonNull ParcelFileDescriptor fd) {
- mFd = fd;
+ public void setUri(@NonNull Uri uri) {
+ mUri = uri;
}
/**
@@ -269,11 +233,7 @@
mAxes = in.createTypedArray(Axis.CREATOR);
mWeight = in.readInt();
mIsItalic = in.readInt() == 1;
- if (in.readInt() == 1) { /* has FD */
- mFd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
- } else {
- mFd = null;
- }
+ mUri = in.readTypedObject(Uri.CREATOR);
}
@Override
@@ -283,10 +243,7 @@
out.writeTypedArray(mAxes, flag);
out.writeInt(mWeight);
out.writeInt(mIsItalic ? 1 : 0);
- out.writeInt(mFd == null ? 0 : 1);
- if (mFd != null) {
- mFd.writeToParcel(out, flag);
- }
+ out.writeTypedObject(mUri, flag);
}
@Override
@@ -425,22 +382,6 @@
}
/**
- * For duplicating file descriptor underlying Font object.
- *
- * This copy constructor is not for deep copying.
- * @hide
- */
- public Family(Family origin) {
- mName = origin.mName;
- mLanguage = origin.mLanguage;
- mVariant = origin.mVariant;
- mFonts = new Font[origin.mFonts.length];
- for (int i = 0; i < origin.mFonts.length; ++i) {
- mFonts[i] = new Font(origin.mFonts[i]);
- }
- }
-
- /**
* Returns the name given by the system to this font family.
*/
public @Nullable String getName() {
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index 87231e1..44019c32 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -17,6 +17,7 @@
package android.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.ParcelableException;
import com.android.internal.util.Preconditions;
@@ -55,10 +56,26 @@
return getCompleteMessage(null, t);
}
+ public static <E extends Throwable> void propagateIfInstanceOf(
+ @Nullable Throwable t, Class<E> c) throws E {
+ if (t != null && c.isInstance(t)) {
+ throw c.cast(t);
+ }
+ }
+
+ /**
+ * @param <E> a checked exception that is ok to throw without wrapping
+ */
+ public static <E extends Exception> RuntimeException propagate(@NonNull Throwable t, Class<E> c)
+ throws E {
+ propagateIfInstanceOf(t, c);
+ return propagate(t);
+ }
+
public static RuntimeException propagate(@NonNull Throwable t) {
Preconditions.checkNotNull(t);
- if (t instanceof Error) throw (Error)t;
- if (t instanceof RuntimeException) throw (RuntimeException)t;
+ propagateIfInstanceOf(t, Error.class);
+ propagateIfInstanceOf(t, RuntimeException.class);
throw new RuntimeException(t);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6c73b9b..e924f77 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -61,6 +61,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManagerGlobal;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -4170,14 +4171,14 @@
/**
* When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
* request read access to the content URI(s) contained in the {@link ClipData} object.
- * @see android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
+ * @see android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION
*/
public static final int DRAG_FLAG_GLOBAL_URI_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION;
/**
* When this flag is used with {@link #DRAG_FLAG_GLOBAL}, the drag recipient will be able to
* request write access to the content URI(s) contained in the {@link ClipData} object.
- * @see android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ * @see android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION
*/
public static final int DRAG_FLAG_GLOBAL_URI_WRITE = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
@@ -4185,8 +4186,8 @@
* When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
* #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant can be persisted across device
* reboots until explicitly revoked with
- * {@link android.content.Context#revokeUriPermission(Uri,int) Context.revokeUriPermission}.
- * @see android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ * {@link android.content.Context#revokeUriPermission(Uri, int)} Context.revokeUriPermission}.
+ * @see android.content.Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION
*/
public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION =
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
@@ -4195,7 +4196,7 @@
* When this flag is used with {@link #DRAG_FLAG_GLOBAL_URI_READ} and/or {@link
* #DRAG_FLAG_GLOBAL_URI_WRITE}, the URI permission grant applies to any URI that is a prefix
* match against the original granted URI.
- * @see android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
+ * @see android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION
*/
public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION =
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
@@ -7895,7 +7896,7 @@
* @param arguments A {@link Bundle} holding any arguments relevant for this request. May be
* {@code null} if the service provided no arguments.
*
- * @see AccessibilityNodeInfo#setExtraAvailableData
+ * @see AccessibilityNodeInfo#setAvailableExtraData(List)
*/
public void addExtraDataToAccessibilityNodeInfo(
@NonNull AccessibilityNodeInfo info, @NonNull String extraDataKey,
@@ -9839,7 +9840,7 @@
* Focus gets restored for a view hierarchy when the root of the hierarchy gets added to a
* window or serves as a target of cluster navigation.
*
- * @see #restoreDefaultFocus(int)
+ * @see #restoreDefaultFocus()
*
* @return {@code true} if this view is the default-focus view, {@code false} otherwise
* @attr ref android.R.styleable#View_focusedByDefault
@@ -9859,7 +9860,7 @@
* @param isFocusedByDefault {@code true} to set this view as the default-focus view,
* {@code false} otherwise.
*
- * @see #restoreDefaultFocus(int)
+ * @see #restoreDefaultFocus()
*
* @attr ref android.R.styleable#View_focusedByDefault
*/
@@ -16457,29 +16458,34 @@
}
/**
- * @see #onMovedToDisplay(int)
+ * @see #onMovedToDisplay(int, Configuration)
*/
- void dispatchMovedToDisplay(Display display) {
+ void dispatchMovedToDisplay(Display display, Configuration config) {
mAttachInfo.mDisplay = display;
mAttachInfo.mDisplayState = display.getState();
- onMovedToDisplay(display.getDisplayId());
+ onMovedToDisplay(display.getDisplayId(), config);
}
/**
* Called by the system when the hosting activity is moved from one display to another without
* recreation. This means that the activity is declared to handle all changes to configuration
* that happened when it was switched to another display, so it wasn't destroyed and created
- * again. This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
- * applied configuration actually changed.
+ * again.
+ *
+ * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+ * applied configuration actually changed. It is up to app developer to choose whether to handle
+ * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+ * call.
*
* <p>Use this callback to track changes to the displays if some functionality relies on an
* association with some display properties.
*
* @param displayId The id of the display to which the view was moved.
+ * @param config Configuration of the resources on new display after move.
*
* @see #onConfigurationChanged(Configuration)
*/
- public void onMovedToDisplay(int displayId) {
+ public void onMovedToDisplay(int displayId, Configuration config) {
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index de0ec40..7921938 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3281,13 +3281,13 @@
}
@Override
- void dispatchMovedToDisplay(Display display) {
- super.dispatchMovedToDisplay(display);
+ void dispatchMovedToDisplay(Display display, Configuration config) {
+ super.dispatchMovedToDisplay(display, config);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
- children[i].dispatchMovedToDisplay(display);
+ children[i].dispatchMovedToDisplay(display, config);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1681787..cf52c60 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1106,10 +1106,11 @@
/**
* Notify about move to a different display.
* @param displayId The id of the display where this view root is moved to.
+ * @param config Configuration of the resources on new display after move.
*
* @hide
*/
- public void onMovedToDisplay(int displayId) {
+ public void onMovedToDisplay(int displayId, Configuration config) {
if (mDisplay.getDisplayId() == displayId) {
return;
}
@@ -1120,7 +1121,7 @@
mView.getResources());
mAttachInfo.mDisplayState = mDisplay.getState();
// Internal state updated, now notify the view hierarchy.
- mView.dispatchMovedToDisplay(mDisplay);
+ mView.dispatchMovedToDisplay(mDisplay, config);
}
void pokeDrawLockIfNeeded() {
@@ -3485,15 +3486,16 @@
mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId);
} else {
// There is no activity callback - update the configuration right away.
- updateConfiguration();
+ updateConfiguration(newDisplayId);
}
mForceNextConfigUpdate = false;
}
/**
* Update display and views if last applied merged configuration changed.
+ * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} otherwise.
*/
- public void updateConfiguration() {
+ public void updateConfiguration(int newDisplayId) {
if (mView == null) {
return;
}
@@ -3503,6 +3505,13 @@
// the one in them which may be newer.
final Resources localResources = mView.getResources();
final Configuration config = localResources.getConfiguration();
+
+ // Handle move to display.
+ if (newDisplayId != INVALID_DISPLAY) {
+ onMovedToDisplay(newDisplayId, config);
+ }
+
+ // Handle configuration change.
if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {
// Update the display with new DisplayAdjustments.
mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(
@@ -3674,15 +3683,17 @@
SomeArgs args = (SomeArgs) msg.obj;
final int displayId = args.argi3;
- final boolean displayChanged = mDisplay.getDisplayId() != displayId;
- if (displayChanged) {
- onMovedToDisplay(displayId);
- }
-
final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+ final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+
if (mergedConfiguration != null) {
+ // If configuration changed - notify about that and, maybe, about move to
+ // display.
performConfigurationChange(mergedConfiguration, false /* force */,
displayChanged ? displayId : INVALID_DISPLAY /* same display */);
+ } else if (displayChanged) {
+ // Moved to display without config change - report last applied one.
+ onMovedToDisplay(displayId, mLastConfigurationFromResources);
}
final boolean framesChanged = !mWinFrame.equals(args.arg1)
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 44309a7..28c2d01 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -520,27 +520,27 @@
}
private HashMap<String, String> getExtraValueHashMap() {
- if (mExtraValueHashMapCache == null) {
- synchronized(this) {
- if (mExtraValueHashMapCache == null) {
- mExtraValueHashMapCache = new HashMap<String, String>();
- final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
- final int N = pairs.length;
- for (int i = 0; i < N; ++i) {
- final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
- if (pair.length == 1) {
- mExtraValueHashMapCache.put(pair[0], null);
- } else if (pair.length > 1) {
- if (pair.length > 2) {
- Slog.w(TAG, "ExtraValue has two or more '='s");
- }
- mExtraValueHashMapCache.put(pair[0], pair[1]);
- }
+ synchronized (this) {
+ HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
+ if (extraValueMap != null) {
+ return extraValueMap;
+ }
+ extraValueMap = new HashMap<>();
+ final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
+ for (int i = 0; i < pairs.length; ++i) {
+ final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
+ if (pair.length == 1) {
+ extraValueMap.put(pair[0], null);
+ } else if (pair.length > 1) {
+ if (pair.length > 2) {
+ Slog.w(TAG, "ExtraValue has two or more '='s");
}
+ extraValueMap.put(pair[0], pair[1]);
}
}
+ mExtraValueHashMapCache = extraValueMap;
+ return extraValueMap;
}
- return mExtraValueHashMapCache;
}
/**
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 9397a41..f0f39b6 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,8 +64,8 @@
* scores for different collections.
*/
public ClassificationResult[] classifyText(
- String context, int selectionBegin, int selectionEnd) {
- return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+ String context, int selectionBegin, int selectionEnd, int hintFlags) {
+ return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd, hintFlags);
}
/**
@@ -76,7 +81,7 @@
long context, String text, int selectionBegin, int selectionEnd);
private static native ClassificationResult[] nativeClassifyText(
- long context, String text, int selectionBegin, int selectionEnd);
+ long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
private static native void nativeClose(long context);
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 46f7a81..dabbf31 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -70,26 +70,6 @@
public LinksInfo getLinks(CharSequence text, int linkMask, LocaleList defaultLocales) {
return LinksInfo.NO_OP;
}
-
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
};
/**
@@ -154,16 +134,4 @@
*/
LinksInfo getLinks(
@NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales);
-
- // TODO: Remove
- /** @removed */
- TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex);
- // TODO: Remove
- /** @removed */
- TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex);
- // TODO: Remove
- /** @removed */
- LinksInfo getLinks(CharSequence text, int linkMask);
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 06ac869..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,14 +119,18 @@
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, string.subSequence(startIndex, endIndex));
// TODO: Added this log for debug only. Remove before release.
Log.d(LOG_TAG, String.format(
- "Classification type: %s", getHighestScoringType(results)));
- return createClassificationResult(results, classified);
+ "Classification type: %s", classificationResult));
+ return classificationResult;
}
}
} catch (Throwable t) {
@@ -149,26 +156,6 @@
return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
}
- // TODO: Remove
- @Override
- public TextSelection suggestSelection(
- CharSequence text, int selectionStartIndex, int selectionEndIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public TextClassificationResult getTextClassificationResult(
- CharSequence text, int startIndex, int endIndex) {
- throw new UnsupportedOperationException("Removed");
- }
-
- // TODO: Remove
- @Override
- public LinksInfo getLinks(CharSequence text, int linkMask) {
- throw new UnsupportedOperationException("Removed");
- }
-
private SmartSelection getSmartSelection() throws FileNotFoundException {
synchronized (mSmartSelectionLock) {
if (mSmartSelection == null) {
@@ -226,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 "";
@@ -280,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/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 81c2f5d..71db6b1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -280,6 +280,44 @@
}
}
+ /**
+ * If the ApplicationInfo provided is for a stub WebView, fix up the object to include the
+ * required values from the donor package. If the ApplicationInfo is for a full WebView,
+ * leave it alone. Throws MissingWebViewPackageException if the donor is missing.
+ */
+ private static void fixupStubApplicationInfo(ApplicationInfo ai, PackageManager pm) {
+ String donorPackageName = null;
+ if (ai.metaData != null) {
+ donorPackageName = ai.metaData.getString("com.android.webview.WebViewDonorPackage");
+ }
+ if (donorPackageName != null) {
+ PackageInfo donorPackage;
+ try {
+ donorPackage = pm.getPackageInfo(
+ donorPackageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_FACTORY_ONLY);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new MissingWebViewPackageException("Failed to find donor package: " +
+ donorPackageName);
+ }
+ ApplicationInfo donorInfo = donorPackage.applicationInfo;
+
+ // Replace the stub's code locations with the donor's.
+ ai.sourceDir = donorInfo.sourceDir;
+ ai.splitSourceDirs = donorInfo.splitSourceDirs;
+ ai.nativeLibraryDir = donorInfo.nativeLibraryDir;
+ ai.secondaryNativeLibraryDir = donorInfo.secondaryNativeLibraryDir;
+
+ // Copy the donor's primary and secondary ABIs, since the stub doesn't have native code
+ // and so they are unset.
+ ai.primaryCpuAbi = donorInfo.primaryCpuAbi;
+ ai.secondaryCpuAbi = donorInfo.secondaryCpuAbi;
+ }
+ }
+
private static Context getWebViewContextAndSetProvider() {
Application initialApplication = AppGlobals.getInitialApplication();
try {
@@ -307,9 +345,10 @@
}
// Fetch package info and verify it against the chosen package
PackageInfo newPackageInfo = null;
+ PackageManager pm = initialApplication.getPackageManager();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "PackageManager.getPackageInfo()");
try {
- newPackageInfo = initialApplication.getPackageManager().getPackageInfo(
+ newPackageInfo = pm.getPackageInfo(
response.packageInfo.packageName,
PackageManager.GET_SHARED_LIBRARY_FILES
| PackageManager.MATCH_DEBUG_TRIAGED_MISSING
@@ -328,12 +367,15 @@
// failure
verifyPackageInfo(response.packageInfo, newPackageInfo);
+ ApplicationInfo ai = newPackageInfo.applicationInfo;
+ fixupStubApplicationInfo(ai, pm);
+
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"initialApplication.createApplicationContext");
try {
// Construct an app context to load the Java code into the current app.
Context webViewContext = initialApplication.createApplicationContext(
- newPackageInfo.applicationInfo,
+ ai,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
sPackageInfo = newPackageInfo;
return webViewContext;
@@ -449,7 +491,11 @@
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
String[] nativeLibs = null;
+ String originalSourceDir = packageInfo.applicationInfo.sourceDir;
try {
+ fixupStubApplicationInfo(packageInfo.applicationInfo,
+ AppGlobals.getInitialApplication().getPackageManager());
+
nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(packageInfo);
if (nativeLibs != null) {
long newVmSize = 0L;
@@ -498,7 +544,7 @@
Log.e(LOGTAG, "error preparing webview native library", t);
}
- WebViewZygote.onWebViewProviderChanged(packageInfo);
+ WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);
return prepareWebViewInSystemServer(nativeLibs);
}
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index f78d622..2123deb 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -67,6 +67,13 @@
private static PackageInfo sPackage;
/**
+ * Cache key for the selected WebView package's classloader. This is set from
+ * #onWebViewProviderChanged().
+ */
+ @GuardedBy("sLock")
+ private static String sPackageCacheKey;
+
+ /**
* Flag for whether multi-process WebView is enabled. If this is false, the zygote
* will not be started.
*/
@@ -118,9 +125,10 @@
}
}
- public static void onWebViewProviderChanged(PackageInfo packageInfo) {
+ public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
synchronized (sLock) {
sPackage = packageInfo;
+ sPackageCacheKey = cacheKey;
// If multi-process is not enabled, then do not start the zygote service.
if (!sMultiprocessEnabled) {
@@ -210,7 +218,8 @@
TextUtils.join(File.pathSeparator, zipPaths);
Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, Build.SUPPORTED_ABIS[0]);
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
+ Build.SUPPORTED_ABIS[0]);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to " + serviceName, e);
sZygote = null;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1937187..99b91bd 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6873,9 +6873,11 @@
mTransientStateViews.put(position, scrap);
} else {
// Otherwise, we'll have to remove the view and start over.
+ clearScrapForRebind(scrap);
getSkippedScrap().add(scrap);
}
} else {
+ clearScrapForRebind(scrap);
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
@@ -7098,12 +7100,12 @@
}
} else if (params.scrappedFromPosition == position) {
final View scrap = scrapViews.remove(i);
- clearAccessibilityFromScrap(scrap);
+ clearScrapForRebind(scrap);
return scrap;
}
}
final View scrap = scrapViews.remove(size - 1);
- clearAccessibilityFromScrap(scrap);
+ clearScrapForRebind(scrap);
return scrap;
} else {
return null;
@@ -7117,7 +7119,7 @@
}
}
- private void clearAccessibilityFromScrap(View view) {
+ private void clearScrapForRebind(View view) {
view.clearAccessibilityFocus();
view.setAccessibilityDelegate(null);
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 78d18fd..ab4cce4 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -523,9 +523,17 @@
/**
* Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
*
- * @param height Height of the popup window.
+ * @param height Height of the popup window must be a positive value,
+ * {@link #MATCH_PARENT}, or {@link #WRAP_CONTENT}.
+ *
+ * @throws IllegalArgumentException if height is set to negative value
*/
public void setHeight(int height) {
+ if (height < 0 && ViewGroup.LayoutParams.WRAP_CONTENT != height
+ && ViewGroup.LayoutParams.MATCH_PARENT != height) {
+ throw new IllegalArgumentException(
+ "Invalid height. Must be a positive value, MATCH_PARENT, or WRAP_CONTENT.");
+ }
mDropDownHeight = height;
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 1c0c4ef..12e35a1 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1639,7 +1639,7 @@
final View focusChild = getAccessibilityFocusedChild(focusHost);
if (focusChild != null) {
if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
- || focusChild.hasTransientState() || mAdapterHasStableIds) {
+ || (focusChild.hasTransientState() && mAdapterHasStableIds)) {
// The views won't be changing, so try to maintain
// focus on the current host and virtual view.
accessibilityFocusLayoutRestoreView = focusHost;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index b63b899..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);
}
}
@@ -2178,9 +2178,14 @@
update = true;
}
- final View anchor = mAnchor.get();
- final int newAccessibilityIdOfAnchor = (anchor != null)
- ? anchor.getAccessibilityViewId() : -1;
+ View anchor = null;
+ int newAccessibilityIdOfAnchor = -1;
+
+ if (mAnchor != null && mAnchor.get() != null) {
+ anchor = mAnchor.get();
+ newAccessibilityIdOfAnchor = anchor.getAccessibilityViewId();
+ }
+
if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) {
p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor;
update = true;
@@ -2366,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);
@@ -2477,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,
@@ -2493,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) {
@@ -2539,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/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 7ce5fc3..b3904f4 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -477,12 +477,9 @@
final KeyCharacterMap kmap = KeyCharacterMap.load(
event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
menu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC);
- menu.performShortcut(keyCode, event, 0);
+ return menu.performShortcut(keyCode, event, 0);
}
- // This action bar always returns true for handling keyboard shortcuts.
- // This will block the window from preparing a temporary panel to handle
- // keyboard shortcuts.
- return true;
+ return false;
}
@Override
@@ -525,6 +522,17 @@
}
return result;
}
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+ // This gets called by PhoneWindow.preparePanel. Since this already manages
+ // its own panel, we return a dummy view here to prevent PhoneWindow from
+ // preparing a default one.
+ return new View(mDecorToolbar.getContext());
+ }
+ return super.onCreatePanelView(featureId);
+ }
}
private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 3603b6d..8edd637 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -19,16 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ProxyFileDescriptorCallback;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-
-import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.ThreadFactory;
public class FuseAppLoop {
@@ -42,14 +42,21 @@
return new Thread(r, TAG);
}
};
+ private static final int FUSE_OK = 0;
private final Object mLock = new Object();
private final int mMountPointId;
private final Thread mThread;
+ private final Handler mDefaultHandler;
+
+ private static final int CMD_FSYNC = 1;
@GuardedBy("mLock")
private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final BytesMap mBytesMap = new BytesMap();
+
/**
* Sequential number can be used as file name and inode in AppFuse.
* 0 is regarded as an error, 1 is mount point. So we start the number from 2.
@@ -57,38 +64,40 @@
@GuardedBy("mLock")
private int mNextInode = MIN_INODE;
- private FuseAppLoop(
+ @GuardedBy("mLock")
+ private long mInstance;
+
+ public FuseAppLoop(
int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
mMountPointId = mountPointId;
- final int rawFd = fd.detachFd();
if (factory == null) {
factory = sDefaultThreadFactory;
}
- mThread = factory.newThread(new Runnable() {
- @Override
- public void run() {
- // rawFd is closed by native_start_loop. Java code does not need to close it.
- native_start_loop(rawFd);
+ mInstance = native_new(fd.detachFd());
+ mThread = factory.newThread(() -> {
+ native_start(mInstance);
+ synchronized (mLock) {
+ native_delete(mInstance);
+ mInstance = 0;
+ mBytesMap.clear();
}
});
+ mThread.start();
+ mDefaultHandler = null;
}
- public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
- @Nullable ThreadFactory factory) {
- Preconditions.checkNotNull(fd);
- final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
- loop.mThread.start();
- return loop;
- }
-
- public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
- throws UnmountedException, IOException {
- if (mThread.getState() == Thread.State.TERMINATED) {
- throw new UnmountedException();
- }
+ public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
+ @NonNull Handler handler) throws FuseUnavailableMountException {
synchronized (mLock) {
- if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
- throw new IOException("Too many opened files.");
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ Preconditions.checkState(
+ mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
+ Preconditions.checkArgument(
+ Thread.currentThread().getId() != handler.getLooper().getThread().getId(),
+ "Handler must be different from the current thread");
+ if (mInstance == 0) {
+ throw new FuseUnavailableMountException(mMountPointId);
}
int id;
while (true) {
@@ -101,118 +110,171 @@
break;
}
}
- mCallbackMap.put(id, new CallbackEntry(callback));
+ mCallbackMap.put(id, new CallbackEntry(callback, handler));
return id;
}
}
public void unregisterCallback(int id) {
- mCallbackMap.remove(id);
+ synchronized (mLock) {
+ mCallbackMap.remove(id);
+ }
}
public int getMountPointId() {
return mMountPointId;
}
- private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
- final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
- if (entry != null) {
- return entry;
- } else {
- throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT);
- }
- }
+ // Defined in fuse.h
+ private static final int FUSE_LOOKUP = 1;
+ private static final int FUSE_GETATTR = 3;
+ private static final int FUSE_OPEN = 14;
+ private static final int FUSE_READ = 15;
+ private static final int FUSE_WRITE = 16;
+ private static final int FUSE_RELEASE = 18;
+ private static final int FUSE_FSYNC = 20;
+
+ // Defined in FuseBuffer.h
+ private static final int FUSE_MAX_WRITE = 256 * 1024;
// Called by JNI.
@SuppressWarnings("unused")
- private long onGetSize(long inode) {
+ private void onCommand(int command, long unique, long inode, long offset, int size,
+ byte[] data) {
synchronized(mLock) {
try {
- return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
- } catch (ErrnoException exp) {
- return getError(exp);
+ final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
+ entry.postRunnable(() -> {
+ try {
+ switch (command) {
+ case FUSE_LOOKUP: {
+ final long fileSize = entry.callback.onGetSize();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyLookup(mInstance, unique, inode, fileSize);
+ }
+ }
+ break;
+ }
+ case FUSE_GETATTR: {
+ final long fileSize = entry.callback.onGetSize();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyGetAttr(mInstance, unique, inode, fileSize);
+ }
+ }
+ break;
+ }
+ case FUSE_READ:
+ final int readSize = entry.callback.onRead(offset, size, data);
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyRead(mInstance, unique, readSize, data);
+ }
+ }
+ break;
+ case FUSE_WRITE:
+ final int writeSize = entry.callback.onWrite(offset, size, data);
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replyWrite(mInstance, unique, writeSize);
+ }
+ }
+ break;
+ case FUSE_FSYNC:
+ entry.callback.onFsync();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, FUSE_OK);
+ }
+ }
+ break;
+ case FUSE_RELEASE:
+ entry.callback.onRelease();
+ synchronized (mLock) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, FUSE_OK);
+ }
+ mBytesMap.stopUsing(entry.getThreadId());
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown FUSE command: " + command);
+ }
+ } catch (Exception error) {
+ Log.e(TAG, "", error);
+ replySimple(unique, getError(error));
+ }
+ });
+ } catch (ErrnoException error) {
+ Log.e(TAG, "", error);
+ replySimpleLocked(unique, getError(error));
}
}
}
// Called by JNI.
@SuppressWarnings("unused")
- private int onOpen(long inode) {
- synchronized(mLock) {
+ private byte[] onOpen(long unique, long inode) {
+ synchronized (mLock) {
try {
final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
if (entry.opened) {
throw new ErrnoException("onOpen", OsConstants.EMFILE);
}
- entry.opened = true;
- // Use inode as file handle. It's OK because AppFuse does not allow to open the same
- // file twice.
- return (int) inode;
- } catch (ErrnoException exp) {
- return getError(exp);
+ if (mInstance != 0) {
+ native_replyOpen(mInstance, unique, /* fh */ inode);
+ entry.opened = true;
+ return mBytesMap.startUsing(entry.getThreadId());
+ }
+ } catch (ErrnoException error) {
+ replySimpleLocked(unique, getError(error));
}
+ return null;
}
}
- // Called by JNI.
- @SuppressWarnings("unused")
- private int onFsync(long inode) {
- synchronized(mLock) {
- try {
- getCallbackEntryOrThrowLocked(inode).callback.onFsync();
- return 0;
- } catch (ErrnoException exp) {
- return getError(exp);
+ private static int getError(@NonNull Exception error) {
+ if (error instanceof ErrnoException) {
+ final int errno = ((ErrnoException) error).errno;
+ if (errno != OsConstants.ENOSYS) {
+ return -errno;
}
}
+ return -OsConstants.EBADF;
+ }
+
+ private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
+ final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
+ if (entry == null) {
+ throw new ErrnoException("getCallbackEntryOrThrowLocked", OsConstants.ENOENT);
+ }
+ return entry;
+ }
+
+ private void replySimple(long unique, int result) {
+ synchronized (mLock) {
+ replySimpleLocked(unique, result);
+ }
}
- // Called by JNI.
- @SuppressWarnings("unused")
- private int onRelease(long inode) {
- synchronized(mLock) {
- try {
- getCallbackEntryOrThrowLocked(inode).callback.onRelease();
- return 0;
- } catch (ErrnoException exp) {
- return getError(exp);
- } finally {
- mCallbackMap.remove(checkInode(inode));
- }
+ private void replySimpleLocked(long unique, int result) {
+ if (mInstance != 0) {
+ native_replySimple(mInstance, unique, result);
}
}
- // Called by JNI.
- @SuppressWarnings("unused")
- private int onRead(long inode, long offset, int size, byte[] bytes) {
- synchronized(mLock) {
- try {
- return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
- } catch (ErrnoException exp) {
- return getError(exp);
- }
- }
- }
+ native long native_new(int fd);
+ native void native_delete(long ptr);
+ native void native_start(long ptr);
- // Called by JNI.
- @SuppressWarnings("unused")
- private int onWrite(long inode, long offset, int size, byte[] bytes) {
- synchronized(mLock) {
- try {
- return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
- } catch (ErrnoException exp) {
- return getError(exp);
- }
- }
- }
-
- private static int getError(@NonNull ErrnoException exp) {
- // Should not return ENOSYS because the kernel stops
- // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
- return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
- }
-
- native boolean native_start_loop(int fd);
+ native void native_replySimple(long ptr, long unique, int result);
+ native void native_replyOpen(long ptr, long unique, long fh);
+ native void native_replyLookup(long ptr, long unique, long inode, long size);
+ native void native_replyGetAttr(long ptr, long unique, long inode, long size);
+ native void native_replyWrite(long ptr, long unique, int size);
+ native void native_replyRead(long ptr, long unique, int size, byte[] bytes);
private static int checkInode(long inode) {
Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
@@ -223,10 +285,61 @@
private static class CallbackEntry {
final ProxyFileDescriptorCallback callback;
+ final Handler handler;
boolean opened;
- CallbackEntry(ProxyFileDescriptorCallback callback) {
- Preconditions.checkNotNull(callback);
- this.callback = callback;
+
+ CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
+ this.callback = Preconditions.checkNotNull(callback);
+ this.handler = Preconditions.checkNotNull(handler);
+ }
+
+ long getThreadId() {
+ return handler.getLooper().getThread().getId();
+ }
+
+ void postRunnable(Runnable runnable) throws ErrnoException {
+ final boolean result = handler.post(runnable);
+ if (!result) {
+ throw new ErrnoException("postRunnable", OsConstants.EBADF);
+ }
+ }
+ }
+
+ /**
+ * Entry for bytes map.
+ */
+ private static class BytesMapEntry {
+ int counter = 0;
+ byte[] bytes = new byte[FUSE_MAX_WRITE];
+ }
+
+ /**
+ * Map between Thread ID and byte buffer.
+ */
+ private static class BytesMap {
+ final Map<Long, BytesMapEntry> mEntries = new HashMap<>();
+
+ byte[] startUsing(long threadId) {
+ BytesMapEntry entry = mEntries.get(threadId);
+ if (entry == null) {
+ entry = new BytesMapEntry();
+ mEntries.put(threadId, entry);
+ }
+ entry.counter++;
+ return entry.bytes;
+ }
+
+ void stopUsing(long threadId) {
+ final BytesMapEntry entry = mEntries.get(threadId);
+ Preconditions.checkNotNull(entry);
+ entry.counter--;
+ if (entry.counter <= 0) {
+ mEntries.remove(threadId);
+ }
+ }
+
+ void clear() {
+ mEntries.clear();
}
}
}
diff --git a/media/java/android/media/UnsupportedCasException.java b/core/java/com/android/internal/os/FuseUnavailableMountException.java
similarity index 66%
rename from media/java/android/media/UnsupportedCasException.java
rename to core/java/com/android/internal/os/FuseUnavailableMountException.java
index 3167637..ca3cfb9 100644
--- a/media/java/android/media/UnsupportedCasException.java
+++ b/core/java/com/android/internal/os/FuseUnavailableMountException.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package android.media;
+package com.android.internal.os;
/**
- * Exception thrown when an attempt is made to construct a MediaCas object
- * using a CA_system_id that is not supported by the device
+ * Exception occurred when the mount point has already been unavailable.
*/
-public final class UnsupportedCasException extends MediaCasException {
- public UnsupportedCasException(String detailMessage) {
- super(detailMessage);
+public class FuseUnavailableMountException extends Exception {
+ public FuseUnavailableMountException(int mountId) {
+ super("AppFuse mount point " + mountId + " is unavailable");
}
}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index f27c0d4..cc3f58c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
+import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -67,16 +68,20 @@
}
@Override
- protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+ protected boolean handlePreloadPackage(String packagePath, String libsPath,
+ String cacheKey) {
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
// This enables us to preload Java and native code in the webview zygote process and
// have the preloaded versions actually be used post-fork.
ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
- packagePath, libsPath);
+ packagePath, libsPath, cacheKey);
// Add the APK to the Zygote's list of allowed files for children.
- Zygote.nativeAllowFileAcrossFork(packagePath);
+ String[] packageList = TextUtils.split(packagePath, File.pathSeparator);
+ for (String packageEntry : packageList) {
+ Zygote.nativeAllowFileAcrossFork(packageEntry);
+ }
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index e2485e9..a9bec41 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -177,7 +177,7 @@
if (parsedArgs.preloadPackage != null) {
return handlePreloadPackage(parsedArgs.preloadPackage,
- parsedArgs.preloadPackageLibs);
+ parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
}
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
@@ -314,7 +314,7 @@
return ZygoteInit.isPreloadComplete();
}
- protected boolean handlePreloadPackage(String packagePath, String libsPath) {
+ protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading");
}
@@ -428,6 +428,7 @@
*/
String preloadPackage;
String preloadPackageLibs;
+ String preloadPackageCacheKey;
/**
* Whether this is a request to start preloading the default resources and classes.
@@ -599,6 +600,7 @@
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
+ preloadPackageCacheKey = args[++curArg];
} else if (arg.equals("--preload-default")) {
preloadDefault = true;
} else {
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/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 713287e..8a7d1cf 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -135,6 +135,7 @@
case PublicFormat::DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
case PublicFormat::RAW_SENSOR:
+ case PublicFormat::RAW_DEPTH:
return HAL_PIXEL_FORMAT_RAW16;
default:
// Most formats map 1:1
@@ -149,6 +150,7 @@
return HAL_DATASPACE_V0_JFIF;
case PublicFormat::DEPTH_POINT_CLOUD:
case PublicFormat::DEPTH16:
+ case PublicFormat::RAW_DEPTH:
return HAL_DATASPACE_DEPTH;
case PublicFormat::RAW_SENSOR:
case PublicFormat::RAW_PRIVATE:
@@ -182,8 +184,12 @@
// Enums overlap in both name and value
return static_cast<PublicFormat>(format);
case HAL_PIXEL_FORMAT_RAW16:
- // Name differs, though value is the same
- return PublicFormat::RAW_SENSOR;
+ switch (dataSpace) {
+ case HAL_DATASPACE_DEPTH:
+ return PublicFormat::RAW_DEPTH;
+ default:
+ return PublicFormat::RAW_SENSOR;
+ }
case HAL_PIXEL_FORMAT_RAW_OPAQUE:
// Name differs, though value is the same
return PublicFormat::RAW_PRIVATE;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 6fbf49b..066ce68 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -167,7 +167,7 @@
buffer->getHeight(),
buffer->getPixelFormat(),
buffer->getUsage(),
- (void*)buffer.get());
+ (jlong)buffer.get());
}
static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp
index dd003eb..e125150 100644
--- a/core/jni/com_android_internal_os_FuseAppLoop.cpp
+++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp
@@ -20,140 +20,214 @@
#include <stdlib.h>
#include <sys/stat.h>
+#include <map>
+#include <memory>
+
#include <android_runtime/Log.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <jni.h>
#include <libappfuse/FuseAppLoop.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include "core_jni_helpers.h"
namespace android {
-
namespace {
-
constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop";
jclass gFuseAppLoopClass;
-jmethodID gOnGetSizeMethod;
+jmethodID gOnCommandMethod;
jmethodID gOnOpenMethod;
-jmethodID gOnFsyncMethod;
-jmethodID gOnReleaseMethod;
-jmethodID gOnReadMethod;
-jmethodID gOnWriteMethod;
class Callback : public fuse::FuseAppLoopCallback {
private:
- static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead);
- static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t.");
-
+ typedef ScopedLocalRef<jbyteArray> LocalBytes;
JNIEnv* const mEnv;
jobject const mSelf;
- ScopedLocalRef<jbyteArray> mJniBuffer;
-
- template <typename T>
- T checkException(T result) const {
- if (mEnv->ExceptionCheck()) {
- LOGE_EX(mEnv, nullptr);
- mEnv->ExceptionClear();
- return -EIO;
- }
- return result;
- }
+ std::map<uint64_t, std::unique_ptr<LocalBytes>> mBuffers;
public:
Callback(JNIEnv* env, jobject self) :
- mEnv(env),
- mSelf(self),
- mJniBuffer(env, nullptr) {}
+ mEnv(env), mSelf(self) {}
- bool Init() {
- mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
- return mJniBuffer.get();
+ void OnLookup(uint64_t unique, uint64_t inode) override {
+ mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_LOOKUP, unique, inode, 0, 0, nullptr);
+ CHECK(!mEnv->ExceptionCheck());
}
- bool IsActive() override {
- return true;
+ void OnGetAttr(uint64_t unique, uint64_t inode) override {
+ mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_GETATTR, unique, inode, 0, 0, nullptr);
+ CHECK(!mEnv->ExceptionCheck());
}
- int64_t OnGetSize(uint64_t inode) override {
- return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode));
- }
-
- int32_t OnOpen(uint64_t inode) override {
- return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode));
- }
-
- int32_t OnFsync(uint64_t inode) override {
- return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode));
- }
-
- int32_t OnRelease(uint64_t inode) override {
- return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode));
- }
-
- int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
- CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
- const int32_t result = checkException(mEnv->CallIntMethod(
- mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get()));
- if (result <= 0) {
- return result;
- }
- if (result > static_cast<int32_t>(size)) {
- LOG(ERROR) << "Returned size is too large.";
- return -EIO;
+ void OnOpen(uint64_t unique, uint64_t inode) override {
+ const jbyteArray buffer = static_cast<jbyteArray>(mEnv->CallObjectMethod(
+ mSelf, gOnOpenMethod, unique, inode));
+ CHECK(!mEnv->ExceptionCheck());
+ if (buffer == nullptr) {
+ return;
}
- mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
- CHECK(!mEnv->ExceptionCheck());
-
- return checkException(result);
+ mBuffers.insert(std::make_pair(inode, std::unique_ptr<LocalBytes>(
+ new LocalBytes(mEnv, buffer))));
}
- int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
- CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
+ void OnFsync(uint64_t unique, uint64_t inode) override {
+ mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_FSYNC, unique, inode, 0, 0, nullptr);
+ CHECK(!mEnv->ExceptionCheck());
+ }
- mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer));
+ void OnRelease(uint64_t unique, uint64_t inode) override {
+ mBuffers.erase(inode);
+ mEnv->CallVoidMethod(mSelf, gOnCommandMethod, FUSE_RELEASE, unique, inode, 0, 0, nullptr);
+ CHECK(!mEnv->ExceptionCheck());
+ }
+
+ void OnRead(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size) override {
+ CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxRead));
+
+ auto it = mBuffers.find(inode);
+ CHECK(it != mBuffers.end());
+
+ mEnv->CallVoidMethod(
+ mSelf, gOnCommandMethod, FUSE_READ, unique, inode, offset, size,
+ it->second->get());
+ CHECK(!mEnv->ExceptionCheck());
+ }
+
+ void OnWrite(uint64_t unique, uint64_t inode, uint64_t offset, uint32_t size,
+ const void* buffer) override {
+ CHECK_LE(size, static_cast<uint32_t>(fuse::kFuseMaxWrite));
+
+ auto it = mBuffers.find(inode);
+ CHECK(it != mBuffers.end());
+
+ jbyteArray const javaBuffer = it->second->get();
+
+ mEnv->SetByteArrayRegion(javaBuffer, 0, size, static_cast<const jbyte*>(buffer));
CHECK(!mEnv->ExceptionCheck());
- return checkException(mEnv->CallIntMethod(
- mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
+ mEnv->CallVoidMethod(
+ mSelf, gOnCommandMethod, FUSE_WRITE, unique, inode, offset, size, javaBuffer);
+ CHECK(!mEnv->ExceptionCheck());
}
};
-jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) {
- base::unique_fd fd(jfd);
+jlong com_android_internal_os_FuseAppLoop_new(JNIEnv* env, jobject self, jint jfd) {
+ return reinterpret_cast<jlong>(new fuse::FuseAppLoop(base::unique_fd(jfd)));
+}
+
+void com_android_internal_os_FuseAppLoop_delete(JNIEnv* env, jobject self, jlong ptr) {
+ delete reinterpret_cast<fuse::FuseAppLoop*>(ptr);
+}
+
+void com_android_internal_os_FuseAppLoop_start(JNIEnv* env, jobject self, jlong ptr) {
Callback callback(env, self);
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Start(&callback);
+}
- if (!callback.Init()) {
- LOG(ERROR) << "Failed to init callback";
- return JNI_FALSE;
+void com_android_internal_os_FuseAppLoop_replySimple(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jint result) {
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplySimple(unique, result)) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
}
+}
- return fuse::StartFuseAppLoop(fd.release(), &callback);
+void com_android_internal_os_FuseAppLoop_replyOpen(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong fh) {
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyOpen(unique, fh)) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+ }
+}
+
+void com_android_internal_os_FuseAppLoop_replyLookup(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) {
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyLookup(unique, inode, size)) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+ }
+}
+
+void com_android_internal_os_FuseAppLoop_replyGetAttr(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jlong inode, jint size) {
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyGetAttr(
+ unique, inode, size, S_IFREG | 0777)) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+ }
+}
+
+void com_android_internal_os_FuseAppLoop_replyWrite(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size) {
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyWrite(unique, size)) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+ }
+}
+
+void com_android_internal_os_FuseAppLoop_replyRead(
+ JNIEnv* env, jobject self, jlong ptr, jlong unique, jint size, jbyteArray data) {
+ ScopedByteArrayRO array(env, data);
+ CHECK(size >= 0);
+ CHECK(static_cast<size_t>(size) < array.size());
+ if (!reinterpret_cast<fuse::FuseAppLoop*>(ptr)->ReplyRead(unique, size, array.get())) {
+ reinterpret_cast<fuse::FuseAppLoop*>(ptr)->Break();
+ }
}
const JNINativeMethod methods[] = {
{
- "native_start_loop",
- "(I)Z",
- (void *) com_android_internal_os_FuseAppLoop_start_loop
- }
+ "native_new",
+ "(I)J",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_new)
+ },
+ {
+ "native_delete",
+ "(J)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_delete)
+ },
+ {
+ "native_start",
+ "(J)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_start)
+ },
+ {
+ "native_replySimple",
+ "(JJI)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replySimple)
+ },
+ {
+ "native_replyOpen",
+ "(JJJ)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyOpen)
+ },
+ {
+ "native_replyLookup",
+ "(JJJJ)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyLookup)
+ },
+ {
+ "native_replyGetAttr",
+ "(JJJJ)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyGetAttr)
+ },
+ {
+ "native_replyRead",
+ "(JJI[B)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyRead)
+ },
+ {
+ "native_replyWrite",
+ "(JJI)V",
+ reinterpret_cast<void*>(com_android_internal_os_FuseAppLoop_replyWrite)
+ },
};
-
} // namespace
int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) {
gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
- gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J");
- gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I");
- gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I");
- gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I");
- gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I");
- gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I");
+ gOnCommandMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onCommand", "(IJJJI[B)V");
+ gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(JJ)[B");
RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
return 0;
}
-
} // namespace android
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 3f1bdff..d27c5a3 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -53,6 +53,7 @@
RGBA_1010102 = 0x2b,
JPEG = 0x100,
DEPTH_POINT_CLOUD = 0x101,
+ RAW_DEPTH = 0x1002, // @hide
YV12 = 0x32315659,
Y8 = 0x20203859, // @hide
Y16 = 0x20363159, // @hide
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8721f34..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" />
@@ -2312,6 +2316,10 @@
<permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA"
android:protectionLevel="signature" />
+ <!-- @hide Allows an application to change the accessibility volume. -->
+ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to collect frame statistics -->
<permission android:name="android.permission.FRAME_STATS"
android:protectionLevel="signature" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 68e766e..db234e7 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.
@@ -2799,4 +2801,9 @@
<!-- Colon separated list of package names that should be granted Notification Listener access -->
<string name="config_defaultListenerAccessPackages" translatable="false"></string>
+
+ <!-- Maximum size, specified in pixels, to restrain the display space width to. Height and
+ density will be scaled accordingly to maintain aspect ratio. A value of 0 indicates no
+ constraint will be enforced. -->
+ <integer name="config_maxUiWidth">0</integer>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 868e256..4afa8dc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -189,22 +189,23 @@
<!-- Displayed to tell the user that they cannot change the caller ID setting. -->
<string name="CLIRPermanent">You can\'t change the caller ID setting.</string>
- <!-- Displayed to tell the user that data service is blocked by access control. -->
- <string name="RestrictedOnData">Data service is blocked.</string>
- <!-- Displayed to tell the user that emergency service is blocked by access control. -->
- <string name="RestrictedOnEmergency">Emergency service is blocked.</string>
- <!-- Displayed to tell the user that normal service is blocked by access control. -->
- <string name="RestrictedOnNormal">Voice service is blocked.</string>
- <!-- Displayed to tell the user that all emergency and normal voice services are blocked by access control. -->
- <string name="RestrictedOnAllVoice">All voice services are blocked.</string>
- <!-- Displayed to tell the user that sms service is blocked by access control. -->
- <string name="RestrictedOnSms">SMS service is blocked.</string>
- <!-- Displayed to tell the user that voice/data service is blocked by access control. -->
- <string name="RestrictedOnVoiceData">Voice/data services are blocked.</string>
- <!-- Displayed to tell the user that voice and sms service are blocked by access control. -->
- <string name="RestrictedOnVoiceSms">Voice/SMS services are blocked.</string>
- <!-- Displayed to tell the user that all service is blocked by access control. -->
- <string name="RestrictedOnAll">All voice/data/SMS services are blocked.</string>
+ <!-- Notification title to tell the user that data service is blocked by access control. -->
+ <string name="RestrictedOnDataTitle">No data service</string>
+ <!-- Notification title to tell the user that emergency service is blocked by access control. -->
+ <string name="RestrictedOnEmergencyTitle">No emergency service</string>
+ <!-- Notification title to tell the user that normal service is blocked by access control. -->
+ <string name="RestrictedOnNormalTitle">No voice service</string>
+ <!-- Notification title to tell the user that all emergency and normal voice services are blocked by access control. -->
+ <string name="RestrictedOnAllVoiceTitle">No voice/emergency service</string>
+
+ <!-- Notification content to tell the user that data service is blocked by access control. -->
+ <string name="RestrictedOnDataContent">Your carrier has temporarily suspended data service at this location</string>
+ <!-- Notification content to tell the user that emergency service is blocked by access control. -->
+ <string name="RestrictedOnEmergencyContent">Your carrier has temporarily suspended emergency calls at this location</string>
+ <!-- Notification content to tell the user that normal service is blocked by access control. -->
+ <string name="RestrictedOnNormalContent">Your carrier has temporarily suspended voice calls at this location</string>
+ <!-- Notification content to tell the user that all emergency and normal voice services are blocked by access control. -->
+ <string name="RestrictedOnAllVoiceContent">Your carrier has temporarily suspended voice and emergency calls at this location</string>
<!-- Displayed to tell the user that they should switch their network preference. -->
<string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b23c96c..92436f4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -507,12 +507,16 @@
<java-symbol type="string" name="Noon" />
<java-symbol type="string" name="PinMmi" />
<java-symbol type="string" name="PwdMmi" />
- <java-symbol type="string" name="RestrictedOnAllVoice" />
- <java-symbol type="string" name="RestrictedOnData" />
- <java-symbol type="string" name="RestrictedOnEmergency" />
- <java-symbol type="string" name="RestrictedOnNormal" />
<java-symbol type="string" name="NetworkPreferenceSwitchSummary" />
<java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
+ <java-symbol type="string" name="RestrictedOnAllVoiceTitle" />
+ <java-symbol type="string" name="RestrictedOnDataTitle" />
+ <java-symbol type="string" name="RestrictedOnEmergencyTitle" />
+ <java-symbol type="string" name="RestrictedOnNormalTitle" />
+ <java-symbol type="string" name="RestrictedOnAllVoiceContent" />
+ <java-symbol type="string" name="RestrictedOnDataContent" />
+ <java-symbol type="string" name="RestrictedOnEmergencyContent" />
+ <java-symbol type="string" name="RestrictedOnNormalContent" />
<java-symbol type="string" name="SetupCallDefault" />
<java-symbol type="string" name="accept" />
<java-symbol type="string" name="activity_chooser_view_see_all" />
@@ -2919,6 +2923,9 @@
<!-- Colon separated list of package names that should be granted Notification Listener access -->
<java-symbol type="string" name="config_defaultListenerAccessPackages" />
+ <!-- maximum width of the display -->
+ <java-symbol type="integer" name="config_maxUiWidth" />
+
<!-- system notification channels -->
<java-symbol type="string" name="notification_channel_virtual_keyboard" />
<java-symbol type="string" name="notification_channel_physical_keyboard" />
diff --git a/core/tests/coretests/README b/core/tests/coretests/README
index 4a69843..aced441 100644
--- a/core/tests/coretests/README
+++ b/core/tests/coretests/README
@@ -45,6 +45,10 @@
-e debug true
+To uninstall the package:
+
+ adb shell pm uninstall -k com.android.frameworks.coretests
+
For more arguments, see the guide to command=line testing:
https://developer.android.com/studio/test/command-line.html
diff --git a/core/tests/coretests/src/android/content/ContentTests.java b/core/tests/coretests/src/android/content/ContentTests.java
index a1299e3..567b79a 100644
--- a/core/tests/coretests/src/android/content/ContentTests.java
+++ b/core/tests/coretests/src/android/content/ContentTests.java
@@ -23,6 +23,7 @@
TestSuite suite = new TestSuite(ContentTests.class.getName());
suite.addTestSuite(AssetTest.class);
+ suite.addTestSuite(ContentValuesTest.class);
return suite;
}
}
diff --git a/core/tests/coretests/src/android/content/ContentValuesTest.java b/core/tests/coretests/src/android/content/ContentValuesTest.java
new file mode 100644
index 0000000..7b39939
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentValuesTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.content;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+
+/*
+ runtest -c android.content.ContentValuesTest frameworks-core
+
+ or
+
+ make -j256 FrameworksCoreTests && \
+ adb shell pm uninstall -k com.android.frameworks.coretests && \
+ adb install out/target/product/bullhead/testcases/FrameworksCoreTests/FrameworksCoreTests.apk && \
+ adb shell am instrument -w -e package android.content \
+ com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+*/
+public class ContentValuesTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testIsEmpty() throws Exception {
+ ContentValues cv = new ContentValues();
+ assertTrue(cv.isEmpty());
+ assertEquals(0, cv.size());
+
+ cv.put("key", "value");
+ assertFalse(cv.isEmpty());
+ assertEquals(1, cv.size());
+ }
+}
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/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index ff98eb7..fbba6ff 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -264,7 +264,7 @@
final MyThreadFactory factory = new MyThreadFactory();
int firstMountId;
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
- ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) {
+ ParcelFileDescriptor.MODE_READ_ONLY, callback, null, factory)) {
assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
firstMountId = mSm.getProxyFileDescriptorMountPointId();
assertNotSame(-1, firstMountId);
@@ -276,7 +276,7 @@
// StorageManager should mount another bridge on the next open request.
try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
- ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) {
+ ParcelFileDescriptor.MODE_WRITE_ONLY, callback, null, factory)) {
assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId());
}
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/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 33d19d4..d69f67d 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -22,6 +22,7 @@
import android.annotation.HalfFloat;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.util.Half;
@@ -288,7 +289,7 @@
* and <code>(1.0, 0.0, 0.0, 0.5)</code>.</p>
*/
@AnyThread
-public class Color {
+public final class Color {
@ColorInt public static final int BLACK = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
@ColorInt public static final int GRAY = 0xFF888888;
@@ -415,7 +416,7 @@
* to this color space's color model, plus one extra component for
* alpha.
*
- * @return An integer between 4 and 5
+ * @return The integer 4 or 5
*/
@IntRange(from = 4, to = 5)
public int getComponentCount() {
@@ -560,7 +561,37 @@
@NonNull
@Size(min = 4, max = 5)
public float[] getComponents() {
- return Arrays.copyOf(mComponents, mColorSpace.getComponentCount() + 1);
+ return Arrays.copyOf(mComponents, mComponents.length);
+ }
+
+ /**
+ * Copies this color's components in the supplied array. The last element of the
+ * array is always the alpha component.
+ *
+ * @param components An array of floats whose size must be at least
+ * {@link #getComponentCount()}, can be null
+ * @return The array passed as a parameter if not null, or a new array of length
+ * {@link #getComponentCount()}
+ *
+ * @see #getComponent(int)
+ *
+ * @throws IllegalArgumentException If the specified array's length is less than
+ * {@link #getComponentCount()}
+ */
+ @NonNull
+ @Size(min = 4)
+ public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
+ if (components == null) {
+ return Arrays.copyOf(mComponents, mComponents.length);
+ }
+
+ if (components.length < mComponents.length) {
+ throw new IllegalArgumentException("The specified array's length must be at "
+ + "least " + mComponents.length);
+ }
+
+ System.arraycopy(mComponents, 0, components, 0, mComponents.length);
+ return components;
}
/**
@@ -570,7 +601,7 @@
*
* <p>If the requested component index is {@link #getComponentCount()},
* this method returns the alpha component, always in the range
- * \([0..1\).</p>
+ * \([0..1]\).</p>
*
* @see #getComponents()
*
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index b78df34..ff9f11d 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -189,9 +189,8 @@
skip(parser);
}
}
- String fullFilename = "/system/fonts/" +
- FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
- return new FontConfig.Font(fullFilename, index,
+ String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
+ return new FontConfig.Font(sanitizedName, index,
axes.toArray(new FontConfig.Axis[axes.size()]), weight, isItalic);
}
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index a226e85..e3527e3 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -662,6 +662,15 @@
public static final int DEPTH_POINT_CLOUD = 0x101;
/**
+ * Unprocessed implementation-dependent raw
+ * depth measurements, opaque with 16 bit
+ * samples.
+ *
+ * @hide
+ */
+ public static final int RAW_DEPTH = 0x1002;
+
+ /**
* Android private opaque image format.
* <p>
* The choices of the actual format and pixel data layout are entirely up to
@@ -723,6 +732,7 @@
return 24;
case FLEX_RGBA_8888:
return 32;
+ case RAW_DEPTH:
case RAW_SENSOR:
return 16;
case RAW10:
@@ -765,6 +775,7 @@
case DEPTH16:
case DEPTH_POINT_CLOUD:
case PRIVATE:
+ case RAW_DEPTH:
return true;
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 228d950..8c3a2e8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1003,21 +1003,22 @@
Map<String, ByteBuffer> bufferForPath) {
FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
for (FontConfig.Font font : family.getFonts()) {
- ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
+ String fullPathName = "/system/fonts/" + font.getFontName();
+ ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
if (fontBuffer == null) {
- try (FileInputStream file = new FileInputStream(font.getFontName())) {
+ try (FileInputStream file = new FileInputStream(fullPathName)) {
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- bufferForPath.put(font.getFontName(), fontBuffer);
+ bufferForPath.put(fullPathName, fontBuffer);
} catch (IOException e) {
- Log.e(TAG, "Error mapping font file " + font.getFontName());
+ Log.e(TAG, "Error mapping font file " + fullPathName);
continue;
}
}
if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
font.getWeight(), font.isItalic() ? Builder.ITALIC : Builder.NORMAL)) {
- Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
+ Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
}
}
fontFamily.freeze();
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index e8972aa..d154730 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -363,6 +363,7 @@
state.computedState.transform.copyTo(&info.transform[0]);
mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info);
+ if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) {
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/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 8823a92..f6b2912 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -491,6 +491,36 @@
return *mCache.bitmap;
}
+void Tree::updateCache(sk_sp<SkSurface> surface) {
+ if (surface.get()) {
+ mCache.surface = surface;
+ }
+ if (surface.get() || mCache.dirty) {
+ SkSurface* vdSurface = mCache.surface.get();
+ SkCanvas* canvas = vdSurface->getCanvas();
+ float scaleX = vdSurface->width() / mProperties.getViewportWidth();
+ float scaleY = vdSurface->height() / mProperties.getViewportHeight();
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->clear(SK_ColorTRANSPARENT);
+ canvas->scale(scaleX, scaleY);
+ mRootNode->draw(canvas, false);
+ mCache.dirty = false;
+ canvas->flush();
+ }
+}
+
+void Tree::draw(SkCanvas* canvas) {
+ /*
+ * TODO address the following...
+ *
+ * 1) figure out how to set path's as volatile during animation
+ * 2) if mRoot->getPaint() != null either promote to layer (during
+ * animation) or cache in SkSurface (for static content)
+ */
+ canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(),
+ mutateProperties()->getBounds(), getPaint());
+}
+
void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
SkBitmap outCache;
bitmap.getSkBitmap(&outCache);
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 729a4dd..22cfe29 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,6 +31,7 @@
#include <SkPathMeasure.h>
#include <SkRect.h>
#include <SkShader.h>
+#include <SkSurface.h>
#include <cutils/compiler.h>
#include <stddef.h>
@@ -677,15 +678,37 @@
// This should only be called from animations on RT
TreeProperties* mutateProperties() { return &mProperties; }
+ // called from RT only
+ const TreeProperties& properties() const { return mProperties; }
+
// This should always be called from RT.
void markDirty() { mCache.dirty = true; }
bool isDirty() const { return mCache.dirty; }
bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; }
void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; }
+ // Returns true if VD cache surface is big enough. This should always be called from RT and it
+ // works with Skia pipelines only.
+ bool canReuseSurface() {
+ SkSurface* surface = mCache.surface.get();
+ return surface && surface->width() >= mProperties.getScaledWidth()
+ && surface->height() >= mProperties.getScaledHeight();
+ }
+
+ // Draws VD cache into a canvas. This should always be called from RT and it works with Skia
+ // pipelines only.
+ void draw(SkCanvas* canvas);
+
+ // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must
+ // contain a new surface. This should always be called from RT and it works with Skia pipelines
+ // only.
+ void updateCache(sk_sp<SkSurface> surface);
+
private:
struct Cache {
- sk_sp<Bitmap> bitmap;
+ sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software
+ //TODO: use surface instead of bitmap when drawing in software canvas
+ sk_sp<SkSurface> surface; //used only by Skia pipelines
bool dirty = true;
};
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 496f7ba..3ddc09f 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -19,6 +19,7 @@
#include "renderthread/CanvasContext.h"
#include "VectorDrawable.h"
#include "DumpOpsCanvas.h"
+#include "SkiaPipeline.h"
#include <SkImagePriv.h>
@@ -92,6 +93,8 @@
// If any vector drawable in the display list needs update, damage the node.
if (vectorDrawable->isDirty()) {
isDirty = true;
+ static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
+ ->getVectorDrawables()->push_back(vectorDrawable);
}
vectorDrawable->setPropertyChangeWillBeConsumed(true);
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 10c1865..75f1adc 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -25,6 +25,7 @@
#include <SkPictureRecorder.h>
#include <SkPixelSerializer.h>
#include <SkStream.h>
+#include "VectorDrawable.h"
#include <unistd.h>
@@ -40,7 +41,9 @@
Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
-SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { }
+SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
+ mVectorDrawables.reserve(30);
+}
TaskManager* SkiaPipeline::getTaskManager() {
return &mTaskManager;
@@ -74,6 +77,7 @@
const BakedOpRenderer::LightInfo& lightInfo) {
updateLighting(lightGeometry, lightInfo);
ATRACE_NAME("draw layers");
+ renderVectorDrawableCache();
renderLayersImpl(*layerUpdateQueue, opaque);
layerUpdateQueue->clear();
}
@@ -176,10 +180,35 @@
}
};
+void SkiaPipeline::renderVectorDrawableCache() {
+ //render VectorDrawables into offscreen buffers
+ for (auto vd : mVectorDrawables) {
+ sk_sp<SkSurface> surface;
+ if (!vd->canReuseSurface()) {
+#ifndef ANDROID_ENABLE_LINEAR_BLENDING
+ sk_sp<SkColorSpace> colorSpace = nullptr;
+#else
+ sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
+#endif
+ int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth());
+ int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight());
+ SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight,
+ kPremul_SkAlphaType, colorSpace);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
+ info);
+ }
+ vd->updateCache(surface);
+ }
+ mVectorDrawables.clear();
+}
+
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface) {
+ renderVectorDrawableCache();
+
// draw all layers up front
renderLayersImpl(layers, opaque);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index c58fedf..6f5e719 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -49,6 +49,8 @@
const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface);
+ std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
+
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
@@ -119,8 +121,18 @@
const std::vector< sp<RenderNode> >& nodes, const Rect &contentDrawBounds,
sk_sp<SkSurface>);
+ /**
+ * Render mVectorDrawables into offscreen buffers.
+ */
+ void renderVectorDrawableCache();
+
TaskManager mTaskManager;
std::vector<sk_sp<SkImage>> mPinnedImages;
+
+ /**
+ * populated by prepareTree with dirty VDs
+ */
+ std::vector<VectorDrawableRoot*> mVectorDrawables;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 559d268..27edc25 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -126,22 +126,7 @@
return SkRect::MakeLargest();
}
virtual void onDraw(SkCanvas* canvas) override {
- Bitmap& hwuiBitmap = mRoot->getBitmapUpdateIfDirty();
- SkBitmap bitmap;
- hwuiBitmap.getSkBitmap(&bitmap);
- SkPaint* paint = mRoot->getPaint();
- canvas->drawBitmapRect(bitmap, mRoot->mutateProperties()->getBounds(), paint);
- /*
- * TODO we can draw this directly but need to address the following...
- *
- * 1) Add drawDirect(SkCanvas*) to VectorDrawableRoot
- * 2) fix VectorDrawable.cpp's Path::draw to not make a temporary path
- * so that we don't break caching
- * 3) figure out how to set path's as volatile during animation
- * 4) if mRoot->getPaint() != null either promote to layer (during
- * animation) or cache in SkSurface (for static content)
- *
- */
+ mRoot->draw(canvas);
}
private:
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 738c091..33eda96 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -194,6 +194,8 @@
void waitOnFences();
+ IRenderPipeline* getRenderPipeline() { return mRenderPipeline.get(); }
+
private:
CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
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/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index 7ae58a6..e15f5d9 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -17,6 +17,7 @@
#include "tests/common/TestUtils.h"
#include <gtest/gtest.h>
+#include <SkBlurDrawLooper.h>
#include <SkColorMatrixFilter.h>
#include <SkColorSpace.h>
#include <SkImagePriv.h>
@@ -95,3 +96,16 @@
sk_sp<SkColorSpace> sRGB2 = SkColorSpace::MakeSRGB();
ASSERT_EQ(sRGB1.get(), sRGB2.get());
}
+
+TEST(SkiaBehavior, blurDrawLooper) {
+ sk_sp<SkDrawLooper> looper = SkBlurDrawLooper::Make(SK_ColorRED, 5.0f, 3.0f, 4.0f);
+
+ SkDrawLooper::BlurShadowRec blur;
+ bool success = looper->asABlurShadow(&blur);
+ ASSERT_TRUE(success);
+
+ ASSERT_EQ(SK_ColorRED, blur.fColor);
+ ASSERT_EQ(5.0f, blur.fSigma);
+ ASSERT_EQ(3.0f, blur.fOffset.fX);
+ ASSERT_EQ(4.0f, blur.fOffset.fY);
+}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index b60dbd5..0a1de33 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -15,6 +15,7 @@
*/
package android.media;
+import android.util.IntArray;
import com.android.server.LocalServices;
/**
@@ -43,6 +44,8 @@
public abstract void updateRingerModeAffectedStreamsInternal();
+ public abstract void setAccessibilityServiceUids(IntArray uids);
+
public interface RingerModeDelegate {
/** Called when external ringer mode is evaluated, returns the new internal ringer mode */
int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index abf6b20..2a0e04e 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -62,6 +62,7 @@
case ImageFormat.RAW12:
case ImageFormat.DEPTH16:
case ImageFormat.DEPTH_POINT_CLOUD:
+ case ImageFormat.RAW_DEPTH:
return 1;
case ImageFormat.PRIVATE:
return 0;
@@ -103,6 +104,10 @@
throw new IllegalArgumentException(
"Copy of RAW_OPAQUE format has not been implemented");
}
+ if (src.getFormat() == ImageFormat.RAW_DEPTH) {
+ throw new IllegalArgumentException(
+ "Copy of RAW_DEPTH format has not been implemented");
+ }
if (!(dst.getOwner() instanceof ImageWriter)) {
throw new IllegalArgumentException("Destination image is not from ImageWriter. Only"
+ " the images from ImageWriter are writable");
@@ -206,6 +211,7 @@
case PixelFormat.RGB_565:
case ImageFormat.YUY2:
case ImageFormat.Y16:
+ case ImageFormat.RAW_DEPTH:
case ImageFormat.RAW_SENSOR:
case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown
case ImageFormat.DEPTH16:
@@ -253,6 +259,7 @@
case ImageFormat.RAW_SENSOR:
case ImageFormat.RAW10:
case ImageFormat.RAW12:
+ case ImageFormat.RAW_DEPTH:
return new Size(image.getWidth(), image.getHeight());
case ImageFormat.PRIVATE:
return new Size(0, 0);
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/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/TvContract.java b/media/java/android/media/tv/TvContract.java
index 6808b57..7bf69c0 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1321,9 +1321,7 @@
*
* <p>Type: INTEGER (boolean)
* @see Channels#COLUMN_TRANSIENT
- * @hide
*/
- @SystemApi
public static final String COLUMN_TRANSIENT = "transient";
/**
@@ -2164,10 +2162,9 @@
* specified, this value is set to 0 (not transient) by default.
*
* <p>Type: INTEGER (boolean)
- * @see Programs#COLUMN_TRANSIENT
- * @hide
+ * @see PreviewPrograms#COLUMN_TRANSIENT
+ * @see WatchNextPrograms#COLUMN_TRANSIENT
*/
- @SystemApi
public static final String COLUMN_TRANSIENT = "transient";
/**
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/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e49463f..227d804 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -58,6 +58,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
@@ -109,6 +110,11 @@
private final ScanCallback mBLEScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
+ if (DEBUG) {
+ Log.i(LOG_TAG,
+ "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
+ + ")");
+ }
final DeviceFilterPair<ScanResult> deviceFilterPair
= DeviceFilterPair.findMatch(result, mBLEFilters);
if (deviceFilterPair == null) return;
@@ -125,6 +131,10 @@
private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Log.i(LOG_TAG,
+ "BL.onReceive(context = " + context + ", intent = " + intent + ")");
+ }
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
final DeviceFilterPair<BluetoothDevice> deviceFilterPair
= DeviceFilterPair.findMatch(device, mBluetoothFilters);
@@ -180,15 +190,23 @@
}
private void startDiscovery(AssociationRequest request) {
- mRequest = request;
+ if (!request.equals(mRequest)) {
+ mRequest = request;
- mFilters = request.getDeviceFilters();
- mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
- mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+ mFilters = request.getDeviceFilters();
+ mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
- reset();
+ reset();
+ } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
+
+
+
+ if (!ArrayUtils.isEmpty(mDevicesFound)) {
+ onReadyToShowUI();
+ }
if (shouldScan(mBluetoothFilters)) {
final IntentFilter intentFilter = new IntentFilter();
@@ -215,6 +233,7 @@
}
private void reset() {
+ if (DEBUG) Log.i(LOG_TAG, "reset()");
mDevicesFound.clear();
mSelectedDevice = null;
mDevicesAdapter.notifyDataSetChanged();
@@ -228,10 +247,18 @@
private void stopScan() {
if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
- mBluetoothAdapter.cancelDiscovery();
- mBLEScanner.stopScan(mBLEScanCallback);
- unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
- unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+
+ if (shouldScan(mBluetoothFilters)) {
+ mBluetoothAdapter.cancelDiscovery();
+ unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+ }
+ if (shouldScan(mBLEFilters)) {
+ mBLEScanner.stopScan(mBLEScanCallback);
+ }
+ if (shouldScan(mWifiFilters)) {
+ unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
+ }
+
stopSelf();
}
@@ -355,8 +382,15 @@
public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
- final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
- return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
+ final DeviceFilter<T> matchingFilter
+ = CollectionUtils.find(filters, f -> f.matches(dev));
+
+ DeviceFilterPair<T> result = matchingFilter != null
+ ? new DeviceFilterPair<>(dev, matchingFilter)
+ : null;
+ if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
+ ") -> " + result);
+ return result;
}
public String getDisplayName() {
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 0bf1fa4..28f6877 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -223,6 +223,7 @@
<item>Optimized for Audio Quality (990kbps/909kbps)</item>
<item>Balanced Audio And Connection Quality (660kbps/606kbps)</item>
<item>Optimized for Connection Quality (330kbps/303kbps)</item>
+ <item>Best Effort (Adaptive Bit Rate)</item>
</string-array>
<!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
@@ -230,6 +231,7 @@
<item>1000</item>
<item>1001</item>
<item>1002</item>
+ <item>1003</item>
</string-array>
<!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
@@ -237,6 +239,7 @@
<item>Optimized for Audio Quality</item>
<item>Balanced Audio And Connection Quality</item>
<item>Optimized for Connection Quality</item>
+ <item>Best Effort (Adaptive Bit Rate)</item>
</string-array>
<!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cc2dc1b..f14d0d1 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -76,7 +76,7 @@
<!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
<string name="connected_via_network_scorer">Automatically connected via %1$s</string>
<!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
- <string name="connected_via_network_scorer_default">Automatically connected via Network Quality Scorer</string>
+ <string name="connected_via_network_scorer_default">Automatically connected via network rating provider</string>
<!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
<string name="connected_via_passpoint">Connected via %1$s</string>
<!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
index 4b0ab59..b54d7e20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
@@ -106,11 +106,11 @@
public SuggestionParser(
Context context, SharedPreferences sharedPrefs, int orderXml, String smartDismissControl) {
- mContext = context;
- mSuggestionList = (List<SuggestionCategory>) new SuggestionOrderInflater(mContext)
- .parse(orderXml);
- mSharedPrefs = sharedPrefs;
- mSmartDismissControl = smartDismissControl;
+ this(
+ context,
+ sharedPrefs,
+ (List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml),
+ smartDismissControl);
}
public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) {
@@ -118,12 +118,15 @@
}
@VisibleForTesting
- public SuggestionParser(Context context, SharedPreferences sharedPrefs) {
+ public SuggestionParser(
+ Context context,
+ SharedPreferences sharedPrefs,
+ List<SuggestionCategory> suggestionList,
+ String smartDismissControl) {
mContext = context;
- mSuggestionList = new ArrayList<SuggestionCategory>();
+ mSuggestionList = suggestionList;
mSharedPrefs = sharedPrefs;
- mSmartDismissControl = DEFAULT_SMART_DISMISS_CONTROL;
- Log.wtf(TAG, "Only use this constructor for testing");
+ mSmartDismissControl = smartDismissControl;
}
public List<Tile> getSuggestions() {
@@ -134,7 +137,19 @@
List<Tile> suggestions = new ArrayList<>();
final int N = mSuggestionList.size();
for (int i = 0; i < N; i++) {
- readSuggestions(mSuggestionList.get(i), suggestions, isSmartSuggestionEnabled);
+ final SuggestionCategory category = mSuggestionList.get(i);
+ if (category.exclusive) {
+ // If suggestions from an exclusive category are present, parsing is stopped
+ // and only suggestions from that category are displayed. Note that subsequent
+ // exclusive categories are also ignored.
+ List<Tile> exclusiveSuggestions = new ArrayList<>();
+ readSuggestions(category, exclusiveSuggestions, isSmartSuggestionEnabled);
+ if (!exclusiveSuggestions.isEmpty()) {
+ return exclusiveSuggestions;
+ }
+ } else {
+ readSuggestions(category, suggestions, isSmartSuggestionEnabled);
+ }
}
return suggestions;
}
@@ -368,6 +383,7 @@
public String category;
public String pkg;
public boolean multiple;
+ public boolean exclusive;
}
private static class SuggestionOrderInflater {
@@ -377,6 +393,7 @@
private static final String ATTR_CATEGORY = "category";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_MULTIPLE = "multiple";
+ private static final String ATTR_EXCLUSIVE = "exclusive";
private final Context mContext;
@@ -451,6 +468,9 @@
category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);
String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);
category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);
+ String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);
+ category.exclusive =
+ !TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);
return category;
} else {
throw new IllegalArgumentException("Unknown item " + name);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 55c886e..1f86f8b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -95,7 +95,7 @@
private WifiTrackerNetworkCallback mNetworkCallback;
- private boolean mSavedNetworksExist;
+ private int mNumSavedNetworks;
private boolean mRegistered;
/** Updated using main handler. Clone of this collection is returned
@@ -363,11 +363,11 @@
}
/**
- * @return true when there are saved networks on the device, regardless
- * of whether the WifiTracker is tracking saved networks.
+ * Returns the number of saved networks on the device, regardless of whether the WifiTracker
+ * is tracking saved networks.
*/
- public boolean doSavedNetworksExist() {
- return mSavedNetworksExist;
+ public int getNumSavedNetworks() {
+ return mNumSavedNetworks;
}
public boolean isConnected() {
@@ -461,11 +461,12 @@
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
- mSavedNetworksExist = configs.size() != 0;
+ mNumSavedNetworks = 0;
for (WifiConfiguration config : configs) {
if (config.selfAdded && config.numAssociation == 0) {
continue;
}
+ mNumSavedNetworks++;
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
@@ -525,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/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index e100884..46726f2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -395,6 +395,26 @@
}
@Test
+ public void testGetNumSavedNetworks() throws InterruptedException {
+ WifiConfiguration validConfig = new WifiConfiguration();
+ validConfig.SSID = SSID_1;
+ validConfig.BSSID = BSSID_1;
+
+ WifiConfiguration selfAddedNoAssociation = new WifiConfiguration();
+ selfAddedNoAssociation.selfAdded = true;
+ selfAddedNoAssociation.numAssociation = 0;
+ selfAddedNoAssociation.SSID = SSID_2;
+ selfAddedNoAssociation.BSSID = BSSID_2;
+
+ when(mockWifiManager.getConfiguredNetworks())
+ .thenReturn(Arrays.asList(validConfig, selfAddedNoAssociation));
+
+ WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
+
+ assertEquals(1, tracker.getNumSavedNetworks());
+ }
+
+ @Test
public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
diff --git a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
index 1eeafba..0e2ce3b 100644
--- a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
+++ b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml
@@ -15,6 +15,8 @@
-->
<optional-steps>
+ <step category="com.android.settings.suggested.category.DEFERRED_SETUP"
+ exclusive="true" />
<step category="com.android.settings.suggested.category.LOCK_SCREEN" />
<step category="com.android.settings.suggested.category.EMAIL" />
<step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
index 7729dec..d534180 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java
@@ -16,11 +16,12 @@
package com.android.settingslib;
+import static com.google.common.truth.Truth.assertThat;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.preference.PreferenceManager;
@@ -31,59 +32,59 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.res.ResourceLoader;
+import org.robolectric.res.builder.DefaultPackageManager;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
@RunWith(SettingLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SuggestionParserTest {
- @Mock
- private PackageManager mPackageManager;
private Context mContext;
private SuggestionParser mSuggestionParser;
- private SuggestionParser.SuggestionCategory mSuggestioCategory;
+ private SuggestionParser.SuggestionCategory mMultipleCategory;
+ private SuggestionParser.SuggestionCategory mExclusiveCategory;
private List<Tile> mSuggestionsBeforeDismiss;
private List<Tile> mSuggestionsAfterDismiss;
private SharedPreferences mPrefs;
private Tile mSuggestion;
- private List<ResolveInfo> mInfo;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = spy(RuntimeEnvironment.application);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ RuntimeEnvironment.setRobolectricPackageManager(
+ new TestPackageManager(RuntimeEnvironment.getAppResourceLoader()));
+ mContext = RuntimeEnvironment.application;
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mSuggestion = new Tile();
mSuggestion.intent = new Intent("action");
mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
mSuggestion.metaData = new Bundle();
+ mMultipleCategory = new SuggestionParser.SuggestionCategory();
+ mMultipleCategory.category = "category1";
+ mMultipleCategory.multiple = true;
+ mExclusiveCategory = new SuggestionParser.SuggestionCategory();
+ mExclusiveCategory.category = "category2";
+ mExclusiveCategory.exclusive = true;
mSuggestionParser = new SuggestionParser(
- mContext, mPrefs, R.xml.suggestion_ordering, "0,0");
- mSuggestioCategory = new SuggestionParser.SuggestionCategory();
- mSuggestioCategory.category = "category1";
- mSuggestioCategory.multiple = true;
- mInfo = new ArrayList<>();
+ mContext, mPrefs, Arrays.asList(mMultipleCategory, mExclusiveCategory), "0,0");
+
ResolveInfo info1 = TileUtilsTest.newInfo(true, "category1");
info1.activityInfo.packageName = "pkg";
ResolveInfo info2 = TileUtilsTest.newInfo(true, "category1");
info2.activityInfo.packageName = "pkg2";
- mInfo.add(info1);
- mInfo.add(info2);
- when(mPackageManager.queryIntentActivitiesAsUser(
- any(Intent.class), anyInt(), anyInt())).thenReturn(mInfo);
+ ResolveInfo info3 = TileUtilsTest.newInfo(true, "category2");
+ info3.activityInfo.packageName = "pkg3";
+
+ Intent intent1 = new Intent(Intent.ACTION_MAIN).addCategory("category1");
+ Intent intent2 = new Intent(Intent.ACTION_MAIN).addCategory("category2");
+ RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info1);
+ RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent1, info2);
+ RuntimeEnvironment.getRobolectricPackageManager().addResolveInfoForIntent(intent2, info3);
}
@Test
@@ -99,30 +100,64 @@
@Test
public void testGetSuggestions_withoutSmartSuggestions() {
readAndDismissSuggestion(false);
- mSuggestionParser.readSuggestions(mSuggestioCategory, mSuggestionsAfterDismiss, false);
- assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
- assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(1);
+ mSuggestionParser.readSuggestions(mMultipleCategory, mSuggestionsAfterDismiss, false);
+ assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+ assertThat(mSuggestionsAfterDismiss).hasSize(1);
assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0));
}
@Test
public void testGetSuggestions_withSmartSuggestions() {
readAndDismissSuggestion(true);
- assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2);
- assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(2);
+ assertThat(mSuggestionsBeforeDismiss).hasSize(2);
+ assertThat(mSuggestionsAfterDismiss).hasSize(2);
assertThat(mSuggestionsBeforeDismiss).isEqualTo(mSuggestionsAfterDismiss);
}
+ @Test
+ public void testGetSuggestion_exclusiveNotAvailable() {
+ RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory("category2"),
+ "pkg3");
+
+ // If exclusive item is not available, the other categories should be shown
+ final List<Tile> suggestions = mSuggestionParser.getSuggestions();
+ assertThat(suggestions).hasSize(2);
+ assertThat(suggestions.get(0).category).isEqualTo("category1");
+ assertThat(suggestions.get(1).category).isEqualTo("category1");
+ }
+
+ @Test
+ public void testGetSuggestions_exclusive() {
+ final List<Tile> suggestions = mSuggestionParser.getSuggestions();
+ assertThat(suggestions).hasSize(1);
+ assertThat(suggestions.get(0).category).isEqualTo("category2");
+ }
+
private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) {
- mSuggestionsBeforeDismiss = new ArrayList<Tile>();
- mSuggestionsAfterDismiss = new ArrayList<Tile>();
+ mSuggestionsBeforeDismiss = new ArrayList<>();
+ mSuggestionsAfterDismiss = new ArrayList<>();
mSuggestionParser.readSuggestions(
- mSuggestioCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
- if (mSuggestionParser.dismissSuggestion(
- mSuggestionsBeforeDismiss.get(0), isSmartSuggestionEnabled)) {
- mInfo.remove(0);
+ mMultipleCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled);
+ final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
+ if (mSuggestionParser.dismissSuggestion(suggestion, isSmartSuggestionEnabled)) {
+ RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+ new Intent(Intent.ACTION_MAIN).addCategory(suggestion.category),
+ suggestion.intent.getComponent().getPackageName());
}
mSuggestionParser.readSuggestions(
- mSuggestioCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
+ mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
+ }
+
+ private static class TestPackageManager extends DefaultPackageManager {
+
+ TestPackageManager(ResourceLoader appResourceLoader) {
+ super(appResourceLoader);
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+ return super.queryIntentActivities(intent, flags);
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 2d3c4a7..dfbe43b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -16,11 +16,22 @@
package com.android.settingslib.drawer;
-import android.app.ActivityManager;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import android.content.IContentProvider;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -40,34 +51,23 @@
import com.android.settingslib.SuggestionParser;
import com.android.settingslib.TestConfig;
-import com.android.settingslib.drawer.TileUtilsTest;
-import static org.mockito.Mockito.atLeastOnce;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.robolectric.shadows.ShadowApplication;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import org.mockito.ArgumentCaptor;
-
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -179,7 +179,11 @@
false /* checkCategory */);
assertThat(outTiles.size()).isEqualTo(1);
- SuggestionParser parser = new SuggestionParser(mContext, null);
+ SuggestionParser parser = new SuggestionParser(
+ mContext,
+ null,
+ Collections.emptyList(),
+ "0,10");
parser.filterSuggestions(outTiles, 0, false);
assertThat(outTiles.size()).isEqualTo(0);
}
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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e4d71b6..1147f16 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -181,7 +181,7 @@
<uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
<!-- to control accessibility volume -->
- <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
+ <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
<application
android:name=".SystemUIApplication"
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/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index 25d5226..1c087b3 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -43,6 +43,8 @@
android:alpha="0"
android:background="@color/qs_detail_progress_track"
android:src="@drawable/indeterminate_anim"
+ android:scaleType="fitXY"
+ android:translationY="16dp"
/>
<com.android.systemui.qs.NonInterceptingScrollView
@@ -57,6 +59,6 @@
android:layout_height="match_parent"/>
</com.android.systemui.qs.NonInterceptingScrollView>
- <include layout="@layout/qs_detail_buttons"/>
+ <include layout="@layout/qs_detail_buttons" />
</com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 64cac3c..f15475c 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -106,7 +106,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,cell,bt,dnd,flashlight,rotation,battery,airplane
+ wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
</string>
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
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/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d058e78..79190cb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.NightDisplayController;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.assist.AssistManager;
@@ -257,6 +258,8 @@
mProviders.put(VolumeDialogController.class, () ->
new VolumeDialogControllerImpl(mContext));
+ mProviders.put(MetricsLogger.class, () -> new MetricsLogger());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 9a4179f..6571294 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -405,17 +405,17 @@
}
} else {
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
+ if (FIXED_SIZED_SURFACE) {
+ // If the surface is fixed-size, we should only need to
+ // draw it once and then we'll let the window manager
+ // position it appropriately. As such, we no longer needed
+ // the loaded bitmap. Yay!
+ // hw-accelerated renderer retains bitmap for faster rotation
+ unloadWallpaper(false /* forgetSize */);
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
- // If the surface is fixed-size, we should only need to
- // draw it once and then we'll let the window manager
- // position it appropriately. As such, we no longer needed
- // the loaded bitmap. Yay!
- // hw-accelerated renderer retains bitmap for faster rotation
- unloadWallpaper(false /* forgetSize */);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index ebda2e8..ec80745 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -45,14 +45,12 @@
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -61,6 +59,7 @@
import com.android.systemui.R;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -128,8 +127,9 @@
break;
case MESSAGE_UPDATE_ACTIONS: {
final Bundle data = (Bundle) msg.obj;
- setActions(data.getParcelable(EXTRA_STACK_BOUNDS),
- ((ParceledListSlice) data.getParcelable(EXTRA_ACTIONS)).getList());
+ final ParceledListSlice actions = data.getParcelable(EXTRA_ACTIONS);
+ setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null
+ ? actions.getList() : Collections.EMPTY_LIST);
break;
}
case MESSAGE_UPDATE_DISMISS_FRACTION: {
@@ -260,6 +260,7 @@
}
notifyMenuVisibility(true);
updateExpandButtonFromBounds(stackBounds, movementBounds);
+ setDecorViewVisibility(true);
mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
mMenuContainer.getAlpha(), 1f);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
@@ -300,9 +301,7 @@
if (animationFinishedRunnable != null) {
animationFinishedRunnable.run();
}
- if (getSystemService(AccessibilityManager.class).isEnabled()) {
- finish();
- }
+ setDecorViewVisibility(false);
}
});
mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
@@ -411,6 +410,7 @@
}
private void updateDismissFraction(float fraction) {
+ setDecorViewVisibility(true);
int alpha;
if (mMenuVisible) {
mMenuContainer.setAlpha(1-fraction);
@@ -497,4 +497,16 @@
v.removeCallbacks(mFinishRunnable);
v.postDelayed(mFinishRunnable, delay);
}
+
+ /**
+ * Sets the visibility of the root view of the window to disable drawing and touches for the
+ * activity. This differs from {@link Activity#setVisible(boolean)} in that it does not set
+ * the internal mVisibleFromClient state.
+ */
+ private void setDecorViewVisibility(boolean visible) {
+ final View decorView = getWindow().getDecorView();
+ if (decorView != null) {
+ decorView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
}
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 f70d5b4..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;
@@ -104,6 +111,8 @@
private int mImeHeight;
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
+ private boolean mMovementWithinMinimize;
+ private boolean mMovementWithinDismiss;
// Touch state
private final PipTouchState mTouchState;
@@ -167,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);
@@ -187,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();
@@ -366,6 +392,9 @@
* Sets the minimized state.
*/
void setMinimizedStateInternal(boolean isMinimized) {
+ if (!mEnableMinimize) {
+ return;
+ }
setMinimizedState(isMinimized, false /* fromController */);
}
@@ -373,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);
@@ -435,6 +467,8 @@
* Gesture controlling normal movement of the PIP.
*/
private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
+ // Whether the PiP was on the left side of the screen at the start of the gesture
+ private boolean mStartedOnLeft;
@Override
public void onDown(PipTouchState touchState) {
@@ -442,6 +476,10 @@
return;
}
+ mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+ mMovementWithinMinimize = true;
+ mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+
// If the menu is still visible, and we aren't minimized, then just poke the menu
// so that it will timeout after the user stops touching it
if (mMenuController.isMenuVisible() && !mIsMinimized) {
@@ -475,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) {
@@ -493,6 +531,18 @@
if (ENABLE_DISMISS_DRAG_TO_EDGE) {
updateDismissFraction();
}
+
+ final PointF curPos = touchState.getLastTouchPosition();
+ if (mMovementWithinMinimize) {
+ // Track if movement remains near starting edge to identify swipes to minimize
+ mMovementWithinMinimize = mStartedOnLeft
+ ? curPos.x <= mMovementBounds.left + mTmpBounds.width()
+ : curPos.x >= mMovementBounds.right;
+ }
+ if (mMovementWithinDismiss) {
+ // Track if movement remains near the bottom edge to identify swipe to dismiss
+ mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
+ }
return true;
}
return false;
@@ -526,8 +576,15 @@
}
if (touchState.isDragging()) {
- final boolean onLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
- boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */);
+ final PointF vel = touchState.getVelocity();
+ 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 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)) {
mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(),
@@ -536,8 +593,8 @@
MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED,
METRIC_VALUE_DISMISSED_BY_DRAG);
return true;
- } else if (!mIsMinimized && (mMotionHelper.shouldMinimizePip()
- || isFlingTowardsEdge(touchState, onLeft ? 2 : 3))) {
+ } else if (mEnableMinimize &&
+ !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) {
// Pip should be minimized
setMinimizedStateInternal(true);
if (mMenuController.isMenuVisible()) {
@@ -563,9 +620,7 @@
mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds);
}
- final PointF vel = mTouchState.getVelocity();
- final float velocity = PointF.length(vel.x, vel.y);
- if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ if (isFling) {
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
mUpdateScrimListener);
} else {
@@ -585,42 +640,6 @@
};
/**
- * @return whether the gesture ending in {@param vel} is fast enough to be a fling and towards
- * the provided {@param edge} where:
- *
- * 1 = top
- * 2 = left
- * 3 = right
- * 4 = bottom
- */
- private boolean isFlingTowardsEdge(PipTouchState touchState, int edge) {
- final PointF vel = touchState.getVelocity();
- final PointF downPos = touchState.getDownTouchPosition();
- final Rect bounds = mMotionHelper.getBounds();
- final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
- final boolean isFling =
- PointF.length(vel.x, vel.y) > mFlingAnimationUtils.getMinVelocityPxPerSecond();
- if (!isFling) {
- return false;
- }
- switch (edge) {
- case 1: // top
- return !isHorizontal && vel.y < 0
- && downPos.y <= mMovementBounds.top + bounds.height();
- case 2: // left
- return isHorizontal && vel.x < 0
- && downPos.x <= mMovementBounds.left + bounds.width();
- case 3: // right
- return isHorizontal && vel.x > 0
- && downPos.x >= mMovementBounds.right;
- case 4: // bottom
- return !isHorizontal && vel.y > 0
- && downPos.y >= mMovementBounds.bottom;
- }
- return false;
- }
-
- /**
* Updates the current movement bounds based on whether the menu is currently visible.
*/
private void updateMovementBounds(boolean isExpanded) {
@@ -643,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/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a30b03b..6b50764 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -230,12 +230,8 @@
firstPageBuilder.addFloat(brightness, "translationY", heightDiff, 0);
mBrightnessAnimator = new TouchAnimator.Builder()
.addFloat(brightness, "alpha", 0, 1)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
- .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.setStartDelay(.5f)
.build();
- mAllViews.add(mQsPanel.getPageIndicator());
- mAllViews.add(mQsPanel.getDivider());
mAllViews.add(brightness);
} else {
mBrightnessAnimator = null;
@@ -247,7 +243,11 @@
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
.addFloat(tileLayout, "alpha", 0, 1)
+ .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
+ .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
+ mAllViews.add(mQsPanel.getPageIndicator());
+ mAllViews.add(mQsPanel.getDivider());
mAllViews.add(mQsPanel.getFooter().getView());
float px = 0;
float py = 1;
@@ -264,6 +264,8 @@
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
+ .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
+ .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.setListener(mNonFirstPageListener)
.setEndDelay(.5f)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 1709718..9efe224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
@@ -197,7 +199,7 @@
mDetailContent.removeAllViews();
mDetailContent.addView(detailView);
mDetailViews.put(viewCacheIndex, detailView);
- MetricsLogger.visible(mContext, adapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
announceForAccessibility(mContext.getString(
R.string.accessibility_quick_settings_detail,
adapter.getTitle()));
@@ -206,7 +208,7 @@
setVisibility(View.VISIBLE);
} else {
if (mDetailAdapter != null) {
- MetricsLogger.hidden(mContext, mDetailAdapter.getMetricsCategory());
+ Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
}
mClosingDetail = true;
mDetailAdapter = null;
@@ -238,8 +240,12 @@
protected void setupDetailFooter(DetailAdapter adapter) {
final Intent settingsIntent = adapter.getSettingsIntent();
mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
- mDetailSettingsButton.setOnClickListener(v -> Dependency.get(ActivityStarter.class)
- .postStartActivityDismissingKeyguard(settingsIntent, 0));
+ mDetailSettingsButton.setOnClickListener(v -> {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
+ mDetailAdapter.getMetricsCategory());
+ Dependency.get(ActivityStarter.class)
+ .postStartActivityDismissingKeyguard(settingsIntent, 0);
+ });
}
protected void setupDetailHeader(final DetailAdapter adapter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 2202b58..d51fe8a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -49,6 +51,7 @@
import com.android.systemui.statusbar.phone.ExpandableIndicator;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -160,11 +163,10 @@
final Builder builder = new Builder()
.addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0)
.addFloat(mSettingsButton, "rotation", -120, 0)
- .addFloat(mAlarmStatus, "alpha", 0, 1)
- .addFloat(mAlarmStatus, "translationX", 0, -mDate.getWidth())
- .addFloat(mAlarmStatusCollapsed, "translationX", 0, -mDate.getWidth());
+ .addFloat(mAlarmStatus, "alpha", 0, 1);
if (mAlarmShowing) {
- builder.addFloat(mDate, "alpha", 1, 0);
+ builder.addFloat(mDate, "alpha", 1, 0)
+ .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth());
}
mAnimator = builder.build();
setExpansion(mExpansionAmount);
@@ -336,6 +338,11 @@
@Override
public void onClick(View v) {
if (v == mSettingsButton) {
+ if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+ return;
+ }
MetricsLogger.action(mContext,
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
@@ -358,6 +365,8 @@
startSettingsActivity();
}
} else if (v == mDateTimeGroup) {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+ mNextAlarm != null);
if (mNextAlarm != null) {
PendingIntent showIntent = mNextAlarm.getShowIntent();
mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 63563b2..406f107 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -21,6 +21,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -39,6 +40,8 @@
public class QSFragment extends Fragment implements QS {
private static final String TAG = "QS";
private static final boolean DEBUG = false;
+ private static final String EXTRA_EXPANDED = "expanded";
+ private static final String EXTRA_LISTENING = "listening";
private final Rect mQsBounds = new Rect();
private boolean mQsExpanded;
@@ -85,6 +88,35 @@
mQSCustomizer = view.findViewById(R.id.qs_customize);
mQSCustomizer.setQs(this);
+ if (savedInstanceState != null) {
+ setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
+ setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mListening) {
+ setListening(false);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
+ outState.putBoolean(EXTRA_LISTENING, mListening);
+ }
+
+ @VisibleForTesting
+ boolean isListening() {
+ return mListening;
+ }
+
+ @VisibleForTesting
+ boolean isExpanded() {
+ return mQsExpanded;
}
@Override
@@ -221,7 +253,7 @@
}
// Set bounds on the QS panel so it doesn't run over the header.
- mQsBounds.top = (int) (mHeader.getBottom() * (1 - expansion));
+ mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
mQsBounds.right = mQSPanel.getWidth();
mQsBounds.bottom = mQSPanel.getHeight();
mQSPanel.setClipBounds(mQsBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 29d547c..8596b57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -32,6 +32,8 @@
TileServices getTileServices();
void removeTile(String tileSpec);
+ int indexOf(String tileSpec);
+
interface Callback {
void onTilesChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8298cbb..2e6116d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -59,6 +59,7 @@
protected final View mBrightnessView;
private final H mHandler = new H();
private final View mPageIndicator;
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private int mPanelPaddingBottom;
private int mBrightnessPaddingTop;
@@ -259,7 +260,7 @@
if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
}
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
closeDetail();
} else {
@@ -475,7 +476,7 @@
int newVis = visible ? VISIBLE : INVISIBLE;
setVisibility(newVis);
if (mGridContentVisible != visible) {
- MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, newVis);
}
mGridContentVisible = visible;
}
@@ -483,7 +484,7 @@
private void logTiles() {
for (int i = 0; i < mRecords.size(); i++) {
TileRecord tileRecord = mRecords.get(i);
- MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory());
+ mMetricsLogger.visible(tileRecord.tile.getMetricsCategory());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0ca115e..9330541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -157,6 +157,10 @@
return mServices;
}
+ public int indexOf(String spec) {
+ return mTileSpecs.indexOf(spec);
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6f35017..b5c1bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -22,6 +22,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -155,6 +156,11 @@
return mComponent;
}
+ @Override
+ protected LogMaker populate(LogMaker logMaker) {
+ return super.populate(logMaker).setComponentName(mComponent);
+ }
+
public Tile getQsTile() {
return mTile;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index a751ef4..6e2add4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -84,12 +84,14 @@
protected void updateIcon(ImageView iv, State state) {
if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
+ boolean shouldAnimate = iv.isShown() && mAnimationEnabled
+ && iv.getDrawable() != null;
Drawable d = state.icon != null
- ? iv.isShown() && mAnimationEnabled ? state.icon.getDrawable(mContext)
+ ? shouldAnimate ? state.icon.getDrawable(mContext)
: state.icon.getInvisibleDrawable(mContext) : null;
int padding = state.icon != null ? state.icon.getPadding() : 0;
if (d != null) {
- d.setAutoMirrored(true);
+ d.setAutoMirrored(false);
}
iv.setImageDrawable(d);
iv.setTag(R.id.qs_icon_tag, state.icon);
@@ -114,7 +116,7 @@
if (state.state != mState) {
int color = getColor(state.state);
mState = state.state;
- if (iv.isShown()) {
+ if (iv.isShown() && mTint != 0) {
animateGrayScale(mTint, color, iv);
mTint = color;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 948954c2..1aa51b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -14,12 +14,19 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -29,7 +36,6 @@
import android.util.SparseArray;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
@@ -58,6 +64,7 @@
protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
private final ArraySet<Object> mListeners = new ArraySet<>();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
protected TState mState = newTileState();
@@ -76,7 +83,7 @@
/**
* Declare the category of this tile.
*
- * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
+ * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
* by editing frameworks/base/proto/src/metrics_constants.proto.
*/
abstract public int getMetricsCategory();
@@ -152,17 +159,28 @@
}
public void click() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.CLICK);
}
public void secondaryClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
}
public void longClick() {
+ mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));
mHandler.sendEmptyMessage(H.LONG_CLICK);
}
+ protected LogMaker populate(LogMaker logMaker) {
+ if (mState instanceof BooleanState) {
+ logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
+ }
+ return logMaker.setSubtype(getMetricsCategory())
+ .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
+ }
+
public void showDetail(boolean show) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
}
@@ -224,7 +242,6 @@
}
protected void handleLongClick() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
getLongClickIntent(), 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index c7979d8..8209ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -41,8 +41,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.DetailAdapter;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -102,7 +102,7 @@
@Override
public CharSequence getTileLabel() {
- return mContext.getString(R.string.battery);
+ return mContext.getString(R.string.battery_detail_switch_title);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index ed6e6ef..4e4de15 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -83,7 +83,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
final boolean isEnabled = (Boolean)mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setBluetoothEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a1d3d26..22b6a63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -108,13 +108,11 @@
protected void handleClick() {
if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
mHost.openPanels();
});
return;
}
- MetricsLogger.action(mContext, getMetricsCategory());
showDetail(true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4351b2c..04be7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -98,7 +98,6 @@
@Override
protected void handleSecondaryClick() {
- MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index e33b680..5b374b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -84,7 +84,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mSetting.setValue(mState.value ? 0 : 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7a25140..b796451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -87,7 +87,6 @@
private void toggleDataSaver() {
mState.value = !mDataSaverController.isDataSaverEnabled();
- MetricsLogger.action(mContext, getMetricsCategory(), mState.value);
mDataSaverController.setDataSaverEnabled(mState.value);
refreshState(mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index f35de68..3c2e897 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -146,7 +146,6 @@
Toast.LENGTH_LONG).show();
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
showDetail(true);
int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
mController.setZen(zen, null, TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index 7b0fd73..6d2aa90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -87,7 +87,6 @@
if (ActivityManager.isUserAMonkey()) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
boolean newState = !mState.value;
refreshState(newState);
mFlashlightController.setFlashlight(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6662937..5c3f65c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -106,7 +106,6 @@
if (!isEnabled && mAirplaneMode.getValue() != 0) {
return;
}
- MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setHotspotEnabled(!isEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index c953363..00cfbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -92,7 +92,6 @@
@Override
protected void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), mIntentPackage);
sendIntent("click", mOnClick, mOnClickUri);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index b5c02cb..b11b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -81,13 +81,11 @@
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
final boolean wasEnabled = mState.value;
mHost.openPanels();
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
});
return;
}
final boolean wasEnabled = mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
mController.setLocationEnabled(!wasEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 3299339..d147b69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -85,7 +85,6 @@
@Override
protected void handleClick() {
if (mAdapter == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
if (!mAdapter.isEnabled()) {
mAdapter.enable();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 8b47216..8aa1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -54,7 +54,6 @@
@Override
protected void handleClick() {
final boolean activated = !mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), activated);
mController.setActivated(activated);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 130304f..fb937bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -79,7 +79,6 @@
@Override
protected void handleClick() {
if (mController == null) return;
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
final boolean newState = !mState.value;
mController.setRotationLocked(!newState);
refreshState(newState);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index fde2e04..79b4c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -114,7 +114,6 @@
protected void handleClick() {
// Secondary clicks are header clicks, just toggle.
mState.copyTo(mStateBeforeClick);
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mController.setWifiEnabled(!mState.value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 5086091..6c89241 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -68,7 +68,6 @@
@Override
public void handleClick() {
- MetricsLogger.action(mContext, getMetricsCategory(), !mState.value);
mProfileController.setWorkModeEnabled(!mState.value);
}
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/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index b5f56c3..4d99a46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
@@ -33,7 +34,7 @@
private ArrayMap<Integer, Integer> mLegacyMap;
private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
.setType(MetricsEvent.TYPE_ACTION);
- private MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
public LockscreenGestureLogger() {
mLegacyMap = new ArrayMap<>(EventLogConstants.METRICS_GESTURE_TYPE_MAP.length);
@@ -58,9 +59,4 @@
}
return value;
}
-
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 3f7e340..a5d7c57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -417,7 +417,10 @@
getHomeButton().setImageDrawable(mHomeDefaultIcon);
}
- final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
+ // The Accessibility button always overrides the appearance of the IME switcher
+ final boolean showImeButton =
+ !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
+ != 0);
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
getImeSwitchButton().setImageDrawable(mImeIcon);
@@ -545,8 +548,9 @@
mShowAccessibilityButton = visible;
mLongClickableAccessibilityButton = longClickable;
if (visible) {
- // Accessibility button overrides Menu button.
+ // Accessibility button overrides Menu and IME switcher buttons.
setMenuVisibility(false, true);
+ getImeSwitchButton().setVisibility(View.INVISIBLE);
}
getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index bb6c8f2..53ec8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -127,7 +127,6 @@
private boolean mVolumeVisible;
private boolean mCurrentUserSetup;
- private boolean mManagedProfileFocused = false;
private boolean mManagedProfileIconVisible = false;
private boolean mManagedProfileInQuietMode = false;
@@ -439,45 +438,30 @@
}
}
- private void profileChanged(int userId) {
- UserInfo user = null;
- if (userId == UserHandle.USER_CURRENT) {
- try {
- user = ActivityManager.getService().getCurrentUser();
- } catch (RemoteException e) {
- // Ignore
- }
- } else {
- user = mUserManager.getUserInfo(userId);
- }
-
- mManagedProfileFocused = user != null && user.isManagedProfile();
- if (DEBUG) Log.v(TAG, "profileChanged: mManagedProfileFocused: " + mManagedProfileFocused);
- // Actually update the icon later when transition starts.
- }
-
private void updateManagedProfile() {
- if (DEBUG) {
- Log.v(TAG, "updateManagedProfile: mManagedProfileFocused: "
- + mManagedProfileFocused);
- }
- final boolean showIcon;
- if (mManagedProfileFocused && !mKeyguardMonitor.isShowing()) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status,
- mContext.getString(R.string.accessibility_managed_profile));
- } else if (mManagedProfileInQuietMode) {
- showIcon = true;
- mIconController.setIcon(mSlotManagedProfile,
- R.drawable.stat_sys_managed_profile_status_off,
- mContext.getString(R.string.accessibility_managed_profile));
- } else {
- showIcon = false;
- }
- if (mManagedProfileIconVisible != showIcon) {
- mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
- mManagedProfileIconVisible = showIcon;
+ try {
+ final boolean showIcon;
+ final int userId = ActivityManager.getService().getLastResumedActivityUserId();
+ if (mUserManager.isManagedProfile(userId) && !mKeyguardMonitor.isShowing()) {
+ showIcon = true;
+ mIconController.setIcon(mSlotManagedProfile,
+ R.drawable.stat_sys_managed_profile_status,
+ mContext.getString(R.string.accessibility_managed_profile));
+ } else if (mManagedProfileInQuietMode) {
+ showIcon = true;
+ mIconController.setIcon(mSlotManagedProfile,
+ R.drawable.stat_sys_managed_profile_status_off,
+ mContext.getString(R.string.accessibility_managed_profile));
+ } else {
+ showIcon = false;
+ }
+ if (mManagedProfileIconVisible != showIcon) {
+ mIconController.setIconVisibility(mSlotManagedProfile, showIcon);
+ mManagedProfileIconVisible = showIcon;
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "updateManagedProfile: ", ex);
+ // ignore
}
}
@@ -556,35 +540,16 @@
new SynchronousUserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mUserInfoController.reloadUserInfo();
- }
- });
+ mHandler.post(() -> mUserInfoController.reloadUserInfo());
}
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateAlarm();
- profileChanged(newUserId);
- updateQuietState();
- updateManagedProfile();
- updateForegroundInstantApps();
- }
- });
- }
-
- @Override
- public void onForegroundProfileSwitch(int newProfileId) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- profileChanged(newProfileId);
- }
+ mHandler.post(() -> {
+ updateAlarm();
+ updateQuietState();
+ updateManagedProfile();
+ updateForegroundInstantApps();
});
}
};
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 b82b113..5370ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -484,7 +484,7 @@
private ScreenPinningRequest mScreenPinningRequest;
- MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
@@ -748,12 +748,6 @@
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
- @VisibleForTesting
- void setMetricsLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = metricsLogger;
- mLockscreenGestureLogger.setMetricsLogger(metricsLogger);
- }
-
@Override
public void start() {
mNetworkController = Dependency.get(NetworkController.class);
@@ -993,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/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 21f9a79..cae76b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -24,6 +24,10 @@
boolean isUserSetup(int currentUser);
int getCurrentUser();
+ default boolean isCurrentUserSetup() {
+ return isUserSetup(getCurrentUser());
+ }
+
interface DeviceProvisionedListener {
default void onDeviceProvisionedChanged() { }
default void onUserSwitched() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 3ee01de..21a96e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.view.Gravity;
/**
* Drawable for {@link KeyButtonView}s which contains an asset for both normal mode and light
@@ -40,6 +41,9 @@
private KeyButtonDrawable(Drawable[] drawables) {
super(drawables);
+ for (int i = 0; i < drawables.length; i++) {
+ setLayerGravity(i, Gravity.CENTER);
+ }
mutate();
mHasDarkDrawable = drawables.length > 1;
setDarkIntensity(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
new file mode 100644
index 0000000..c67cccc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.testing.ViewUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSDetailTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSDetail mQsDetail;
+ private QSPanel mQsPanel;
+ private QuickStatusBarHeader mQuickHeader;
+ private ActivityStarter mActivityStarter;
+ private DetailAdapter mMockDetailAdapter;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setup() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+ mTestableLooper.runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+ mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
+ mQsPanel = mock(QSPanel.class);
+ mQuickHeader = mock(QuickStatusBarHeader.class);
+ mQsDetail.setQsPanel(mQsPanel, mQuickHeader);
+
+ mMockDetailAdapter = mock(DetailAdapter.class);
+ when(mMockDetailAdapter.createDetailView(any(), any(), any()))
+ .thenReturn(mock(View.class));
+ });
+ }
+
+ @Test
+ public void testShowDetail_Metrics() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ verify(mMetricsLogger).visible(eq(mMockDetailAdapter.getMetricsCategory()));
+ mQsDetail.handleShowingDetail(null, 0, 0, false);
+ verify(mMetricsLogger).hidden(eq(mMockDetailAdapter.getMetricsCategory()));
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testMoreSettingsButton() {
+ ViewUtils.attachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+
+ mQsDetail.handleShowingDetail(mMockDetailAdapter, 0, 0, false);
+ mQsDetail.findViewById(android.R.id.button2).performClick();
+
+ int metricsCategory = mMockDetailAdapter.getMetricsCategory();
+ verify(mMetricsLogger).action(eq(ACTION_QS_MORE_SETTINGS), eq(metricsCategory));
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(), anyInt());
+
+ ViewUtils.detachView(mQsDetail);
+ mTestableLooper.processAllMessages();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
new file mode 100644
index 0000000..778ab8e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.R.id;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSFooterTest extends LeakCheckedTest {
+
+ private QSFooter mFooter;
+ private ActivityStarter mActivityStarter;
+ private DeviceProvisionedController mDeviceProvisionedController;
+
+ @Before
+ public void setup() throws Exception {
+ injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+ mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
+ mDeviceProvisionedController = mDependency.injectMockDependency(
+ DeviceProvisionedController.class);
+ TestableLooper.get(this).runWithLooper(() -> {
+ mFooter = (QSFooter) LayoutInflater.from(mContext).inflate(R.layout.qs_footer, null);
+ });
+ }
+
+ @Test
+ public void testSettings_UserNotSetup() {
+ View settingsButton = mFooter.findViewById(id.settings_button);
+ when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
+
+ mFooter.onClick(settingsButton);
+ // Verify Settings wasn't launched.
+ verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index deb31da..673ffc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -15,14 +15,19 @@
package com.android.systemui.qs;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
+import android.app.FragmentController;
+import android.app.FragmentManagerNonConfig;
import android.os.Looper;
+import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import android.os.Parcelable;
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiBaseFragmentTest;
@@ -44,12 +49,15 @@
@RunWithLooper(setAsMainLooper = true)
public class QSFragmentTest extends SysuiBaseFragmentTest {
+ private MetricsLogger mMockMetricsLogger;
+
public QSFragmentTest() {
super(QSFragment.class);
}
@Before
public void addLeakCheckDependencies() {
+ mMockMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("com.android.systemui.statusbar.policy.SplitClockView",
@@ -86,4 +94,23 @@
host.destroy();
processAllMessages();
}
+
+ @Test
+ public void testSaveState() {
+ QSFragment qs = (QSFragment) mFragment;
+
+ mFragments.dispatchResume();
+ processAllMessages();
+
+ qs.setListening(true);
+ qs.setExpanded(true);
+ processAllMessages();
+ recreateFragment();
+ processAllMessages();
+
+ // Get the reference to the new fragment.
+ qs = (QSFragment) mFragment;
+ assertTrue(qs.isListening());
+ assertTrue(qs.isExpanded());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
new file mode 100644
index 0000000..4979684
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSPanelTest extends SysuiTestCase {
+
+ private MetricsLogger mMetricsLogger;
+ private QSPanel mQsPanel;
+ private QSTileHost mHost;
+ private QSCustomizer mCustomizer;
+
+ @Before
+ public void setup() throws Exception {
+ TestableLooper.get(this).runWithLooper(() -> {
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mQsPanel = new QSPanel(mContext, null);
+ mHost = mock(QSTileHost.class);
+ when(mHost.getTiles()).thenReturn(Collections.emptyList());
+ mCustomizer = mock(QSCustomizer.class);
+ mQsPanel.setHost(mHost, mCustomizer);
+ });
+ }
+
+ @Test
+ public void testSetExpanded_Metrics() {
+ mQsPanel.setExpanded(true);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(true));
+ mQsPanel.setExpanded(false);
+ verify(mMetricsLogger).visibility(eq(MetricsEvent.QS_PANEL), eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
new file mode 100644
index 0000000..59483f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.service.quicksettings.Tile;
+import android.testing.AndroidTestingRunner;
+import android.testing.UiThreadTest;
+import android.widget.ImageView;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile.Icon;
+import com.android.systemui.plugins.qs.QSTile.State;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class QSIconViewImplTest extends SysuiTestCase {
+
+ private QSIconViewImpl mIconView;
+
+ @Before
+ public void setup() {
+ mIconView = new QSIconViewImpl(mContext);
+ }
+
+ @Test
+ public void testNoFirstAnimation() {
+ ImageView iv = mock(ImageView.class);
+ State s = new State();
+ when(iv.isShown()).thenReturn(true);
+
+ // No current icon, only the static drawable should be used.
+ s.icon = mock(Icon.class);
+ when(iv.getDrawable()).thenReturn(null);
+ mIconView.updateIcon(iv, s);
+ verify(s.icon, never()).getDrawable(any());
+ verify(s.icon).getInvisibleDrawable(any());
+
+ // Has icon, should use the standard (animated) form.
+ s.icon = mock(Icon.class);
+ when(iv.getDrawable()).thenReturn(mock(Drawable.class));
+ mIconView.updateIcon(iv, s);
+ verify(s.icon).getDrawable(any());
+ verify(s.icon, never()).getInvisibleDrawable(any());
+ }
+
+ @Test
+ public void testNoFirstFade() {
+ ImageView iv = mock(ImageView.class);
+ State s = new State();
+ s.state = Tile.STATE_ACTIVE;
+ int desiredColor = mIconView.getColor(s.state);
+ when(iv.isShown()).thenReturn(true);
+
+ mIconView.setIcon(iv, s);
+ verify(iv).setImageTintList(argThat(stateList -> stateList.getColors()[0] == desiredColor));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
new file mode 100644
index 0000000..9ed9d28c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QSTileHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class QSTileImplTest extends SysuiTestCase {
+
+ public static final int POSITION = 14;
+ private TestableLooper mTestableLooper;
+ private TileImpl mTile;
+ private QSTileHost mHost;
+ private MetricsLogger mMetricsLogger;
+
+ @Before
+ public void setup() throws Exception {
+ String spec = "spec";
+ mTestableLooper = TestableLooper.get(this);
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
+ mHost = mock(QSTileHost.class);
+ when(mHost.indexOf(spec)).thenReturn(POSITION);
+ mTestableLooper.runWithLooper(() -> {
+ mTile = new TileImpl(mHost);
+ mTile.setTileSpec(spec);
+ });
+ }
+
+ @Test
+ public void testClick_Metrics() {
+ mTile.click();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK)));
+ }
+
+ @Test
+ public void testSecondaryClick_Metrics() {
+ mTile.secondaryClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
+ }
+
+ @Test
+ public void testLongClick_Metrics() {
+ mTile.longClick();
+ verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS)));
+ }
+
+ @Test
+ public void testPopulate() {
+ LogMaker maker = mock(LogMaker.class);
+ when(maker.setSubtype(anyInt())).thenReturn(maker);
+ mTile.getState().value = true;
+ mTile.populate(maker);
+ verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
+ verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
+ }
+
+ private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
+
+ private final int mCategory;
+ public String mInvalid;
+
+ public TileLogMatcher(int category) {
+ mCategory = category;
+ }
+
+ @Override
+ public boolean matches(LogMaker arg) {
+ if (arg.getCategory() != mCategory) {
+ mInvalid = "Expected category " + mCategory + " but was " + arg.getCategory();
+ return false;
+ }
+ if (arg.getType() != TYPE_ACTION) {
+ mInvalid = "Expected type " + TYPE_ACTION + " but was " + arg.getType();
+ return false;
+ }
+ if (arg.getSubtype() != mTile.getMetricsCategory()) {
+ mInvalid = "Expected subtype " + mTile.getMetricsCategory() + " but was "
+ + arg.getSubtype();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return mInvalid;
+ }
+ }
+
+ private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
+ protected TileImpl(QSHost host) {
+ super(host);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 42;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void setListening(boolean listening) {
+
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return null;
+ }
+ }
+}
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f48af75..bf6b394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -62,9 +62,9 @@
mKeyguardIndicationController = mock(KeyguardIndicationController.class);
mStackScroller = mock(NotificationStackScrollLayout.class);
mMetricsLogger = new FakeMetricsLogger();
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
mKeyguardIndicationController, mStackScroller);
- mStatusBar.setMetricsLogger(mMetricsLogger);
doAnswer(invocation -> {
OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0];
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 783aae7..da441f5 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3656,7 +3656,7 @@
ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870;
// The name of the activity being launched in an app transition event.
- APP_TRANSITION_ACTIVITY_NAME = 871;
+ FIELD_CLASS_NAME = 871;
// ACTION: Settings > App detail > Uninstall
ACTION_SETTINGS_UNINSTALL_APP = 872;
@@ -3874,6 +3874,24 @@
// OPEN: Settings -> System -> Reset options
RESET_DASHBOARD = 924;
+ // ACTION: QS -> Tile clicked
+ ACTION_QS_CLICK = 925;
+
+ // ACTION: QS -> Secondary click
+ ACTION_QS_SECONDARY_CLICK = 926;
+
+ // FIELD: Position info in QS clicks
+ FIELD_QS_POSITION = 927;
+
+ // FIELD: The value of a QS tile when clicked (if applicable)
+ FIELD_QS_VALUE = 928;
+
+ // ACTION: QS -> Detail panel -> more settings
+ ACTION_QS_MORE_SETTINGS = 929;
+
+ // ACTION: QS -> Click date
+ ACTION_QS_DATE = 930;
+
// ---- End O Constants, all O constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 05c6592..acaae7b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -52,6 +52,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.input.InputManager;
+import android.media.AudioManagerInternal;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -75,6 +76,7 @@
import android.provider.SettingsStringUtil.SettingStringHelper;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -220,6 +222,8 @@
private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
new ArrayList<>();
+ private final IntArray mTempIntArray = new IntArray(0);
+
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<>();
@@ -1558,6 +1562,21 @@
}
}
+ final int count = userState.mBoundServices.size();
+ mTempIntArray.clear();
+ for (int i = 0; i < count; i++) {
+ final ResolveInfo resolveInfo =
+ userState.mBoundServices.get(i).mAccessibilityServiceInfo.getResolveInfo();
+ if (resolveInfo != null) {
+ mTempIntArray.add(resolveInfo.serviceInfo.applicationInfo.uid);
+ }
+ }
+ // Calling out with lock held, but to a lower-level service
+ final AudioManagerInternal audioManager =
+ LocalServices.getService(AudioManagerInternal.class);
+ if (audioManager != null) {
+ audioManager.setAccessibilityServiceUids(mTempIntArray);
+ }
updateAccessibilityEnabledSetting(userState);
}
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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 037804e..57d3570 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3537,21 +3537,23 @@
return;
}
mCancelAll = cancelAll;
- // Whoops, the current agent timed out running doBackup(). Tidy up and restage
- // it for the next time we run a backup pass.
- // !!! TODO: keep track of failure counts per agent, and blacklist those which
- // fail repeatedly (i.e. have proved themselves to be buggy).
- Slog.e(TAG, "Cancel backing up " + mCurrentPackage.packageName);
- EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName);
+ final String logPackageName = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName
+ : "no_package_yet";
+ Slog.i(TAG, "Cancel backing up " + logPackageName);
+ EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, logPackageName);
+ addBackupTrace("cancel of " + logPackageName + ", cancelAll=" + cancelAll);
mMonitor = monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
putMonitoringExtra(null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL,
mCancelAll));
- addBackupTrace(
- "cancel of " + mCurrentPackage.packageName + ", cancelAll=" + cancelAll);
errorCleanup();
if (!cancelAll) {
+ // The current agent either timed out or was cancelled running doBackup().
+ // Restage it for the next time we run a backup pass.
+ // !!! TODO: keep track of failure counts per agent, and blacklist those which
+ // fail repeatedly (i.e. have proved themselves to be buggy).
executeNextState(
mQueue.isEmpty() ? BackupState.FINAL : BackupState.RUNNING_QUEUE);
dataChangedImpl(mCurrentPackage.packageName);
diff --git a/services/core/Android.mk b/services/core/Android.mk
index e35a171..099f557 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -22,7 +22,8 @@
services.net \
android.hardware.light@2.0-java \
android.hardware.power@1.0-java \
- android.hardware.tv.cec@1.0-java
+ android.hardware.tv.cec@1.0-java \
+ android.hidl.manager@1.0-java
LOCAL_STATIC_JAVA_LIBRARIES := \
tzdata_shared2 \
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d02b726..e791014 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -96,7 +96,6 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -208,6 +207,8 @@
// See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
+ private MockableSystemProperties mSystemProperties;
+
private Tethering mTethering;
private final PermissionMonitor mPermissionMonitor;
@@ -691,6 +692,8 @@
IpConnectivityLog logger) {
if (DBG) log("ConnectivityService starting up");
+ mSystemProperties = getSystemProperties();
+
mMetricsLog = logger;
mDefaultRequest = createInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
@@ -708,7 +711,7 @@
mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
- mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
+ mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
mContext = checkNotNull(context, "missing Context");
mNetd = checkNotNull(netManager, "missing INetworkManagementService");
@@ -735,7 +738,7 @@
mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
// TODO: What is the "correct" way to do determine if this is a wifi only device?
- boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+ boolean wifiOnly = mSystemProperties.getBoolean("ro.radio.noril", false);
log("wifiOnly=" + wifiOnly);
String[] naStrings = context.getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
@@ -788,8 +791,8 @@
}
}
- mTestMode = SystemProperties.get("cm.test.mode").equals("true")
- && SystemProperties.get("ro.build.type").equals("eng");
+ mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
+ && mSystemProperties.get("ro.build.type").equals("eng");
mTethering = new Tethering(mContext, mNetd, statsService, mPolicyManager,
IoThread.get().getLooper(), new MockableSystemProperties());
@@ -1814,8 +1817,8 @@
// Overridden for testing purposes to avoid writing to SystemProperties.
@VisibleForTesting
- protected int getDefaultTcpRwnd() {
- return SystemProperties.getInt(DEFAULT_TCP_RWND_KEY, 0);
+ protected MockableSystemProperties getSystemProperties() {
+ return new MockableSystemProperties();
}
private void updateTcpBufferSizes(NetworkAgentInfo nai) {
@@ -1853,10 +1856,11 @@
}
Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TCP_DEFAULT_INIT_RWND, getDefaultTcpRwnd());
+ Settings.Global.TCP_DEFAULT_INIT_RWND,
+ mSystemProperties.getInt("net.tcp.default_init_rwnd", 0));
final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
if (rwndValue != 0) {
- SystemProperties.set(sysctlKey, rwndValue.toString());
+ mSystemProperties.set(sysctlKey, rwndValue.toString());
}
}
@@ -1880,7 +1884,7 @@
@Override
public int getRestoreDefaultNetworkDelay(int networkType) {
- String restoreDefaultNetworkDelayStr = SystemProperties.get(
+ String restoreDefaultNetworkDelayStr = mSystemProperties.get(
NETWORK_RESTORE_DELAY_PROP_NAME);
if(restoreDefaultNetworkDelayStr != null &&
restoreDefaultNetworkDelayStr.length() != 0) {
@@ -3081,7 +3085,7 @@
@Override
public boolean isTetheringSupported() {
enforceTetherAccessPermission();
- int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+ int defaultVal = (mSystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
@@ -4580,11 +4584,11 @@
++last;
String key = "net.dns" + last;
String value = dns.getHostAddress();
- SystemProperties.set(key, value);
+ mSystemProperties.set(key, value);
}
for (int i = last + 1; i <= mNumDnsEntries; ++i) {
String key = "net.dns" + i;
- SystemProperties.set(key, "");
+ mSystemProperties.set(key, "");
}
mNumDnsEntries = last;
}
diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java
index 55a945a..f172647 100644
--- a/services/core/java/com/android/server/FontManagerService.java
+++ b/services/core/java/com/android/server/FontManagerService.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.FontListParser;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.FontConfig;
import android.util.Slog;
@@ -34,6 +35,7 @@
public class FontManagerService extends IFontManager.Stub {
private static final String TAG = "FontManagerService";
private static final String FONTS_CONFIG = "/system/etc/fonts.xml";
+ private static final String SYSTEM_FONT_DIR = "/system/fonts/";
@GuardedBy("mLock")
private FontConfig mConfig;
@@ -63,28 +65,22 @@
public FontConfig getSystemFonts() {
synchronized (mLock) {
if (mConfig != null) {
- return new FontConfig(mConfig);
+ return mConfig;
}
- FontConfig config = loadFromSystem();
- if (config == null) {
+ mConfig = loadFromSystem();
+ if (mConfig == null) {
return null;
}
- for (FontConfig.Family family : config.getFamilies()) {
+ for (FontConfig.Family family : mConfig.getFamilies()) {
for (FontConfig.Font font : family.getFonts()) {
- File fontFile = new File(font.getFontName());
- try {
- font.setFd(ParcelFileDescriptor.open(
- fontFile, ParcelFileDescriptor.MODE_READ_ONLY));
- } catch (IOException e) {
- Slog.e(TAG, "Error opening font file " + font.getFontName(), e);
- }
+ File fontFile = new File(SYSTEM_FONT_DIR, font.getFontName());
+ font.setUri(Uri.fromFile(fontFile));
}
}
- mConfig = config;
- return new FontConfig(mConfig);
+ return mConfig;
}
}
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 81219da..c68000a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -98,6 +98,7 @@
import com.android.internal.app.IMediaContainerService;
import com.android.internal.os.AppFuseMount;
import com.android.internal.os.FuseAppLoop;
+import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
@@ -2080,6 +2081,20 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ if ((mask & StorageManager.DEBUG_VIRTUAL_DISK) != 0) {
+ final boolean enabled = (flags & StorageManager.DEBUG_VIRTUAL_DISK) != 0;
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SystemProperties.set(StorageManager.PROP_VIRTUAL_DISK, Boolean.toString(enabled));
+
+ // Reset storage to kick new setting into place
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
@Override
@@ -2993,32 +3008,36 @@
}
}
- private ParcelFileDescriptor mountAppFuse(int uid, int mountId)
- throws NativeDaemonConnectorException {
- final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
- "appfuse", "mount", uid, Process.myPid(), mountId);
- if (event.getFileDescriptors() == null ||
- event.getFileDescriptors().length == 0) {
- throw new NativeDaemonConnectorException("Cannot obtain device FD");
- }
- return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
- }
-
class AppFuseMountScope extends AppFuseBridge.MountScope {
- public AppFuseMountScope(int uid, int pid, int mountId)
- throws NativeDaemonConnectorException {
- super(uid, pid, mountId, mountAppFuse(uid, mountId));
+ boolean opened = false;
+
+ public AppFuseMountScope(int uid, int pid, int mountId) {
+ super(uid, pid, mountId);
+ }
+
+ @Override
+ public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
+ final NativeDaemonEvent event = StorageManagerService.this.mConnector.execute(
+ "appfuse", "mount", uid, Process.myPid(), mountId);
+ opened = true;
+ if (event.getFileDescriptors() == null ||
+ event.getFileDescriptors().length == 0) {
+ throw new NativeDaemonConnectorException("Cannot obtain device FD");
+ }
+ return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
}
@Override
public void close() throws Exception {
- super.close();
- mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
+ if (opened) {
+ mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
+ opened = false;
+ }
}
}
@Override
- public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ public @Nullable AppFuseMount mountProxyFileDescriptorBridge() {
Slog.v(TAG, "mountProxyFileDescriptorBridge");
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -3035,12 +3054,12 @@
final int name = mNextAppFuseName++;
try {
return new AppFuseMount(
- name,
- mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
- } catch (AppFuseBridge.BridgeException e) {
+ name, mAppFuseBridge.addBridge(new AppFuseMountScope(uid, pid, name)));
+ } catch (FuseUnavailableMountException e) {
if (newlyCreated) {
// If newly created bridge fails, it's a real error.
- throw new RemoteException(e.getMessage());
+ Slog.e(TAG, "", e);
+ return null;
}
// It seems the thread of mAppFuseBridge has already been terminated.
mAppFuseBridge = null;
@@ -3053,19 +3072,21 @@
}
@Override
- public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode)
- throws RemoteException {
- Slog.v(TAG, "mountProxyFileDescriptorBridge");
+ public @Nullable ParcelFileDescriptor openProxyFileDescriptor(
+ int mountId, int fileId, int mode) {
+ Slog.v(TAG, "mountProxyFileDescriptor");
final int pid = Binder.getCallingPid();
try {
synchronized (mAppFuseLock) {
if (mAppFuseBridge == null) {
- throw new RemoteException("Cannot find mount point");
+ Slog.e(TAG, "FuseBridge has not been created");
+ return null;
}
return mAppFuseBridge.openFile(pid, mountId, fileId, mode);
}
- } catch (FileNotFoundException | SecurityException | InterruptedException error) {
- throw new RemoteException(error.getMessage());
+ } catch (FuseUnavailableMountException | InterruptedException error) {
+ Slog.v(TAG, "The mount point has already been invalid", error);
+ return null;
}
}
diff --git a/services/core/java/com/android/server/UiThread.java b/services/core/java/com/android/server/UiThread.java
index 1bc6250..fd88d26 100644
--- a/services/core/java/com/android/server/UiThread.java
+++ b/services/core/java/com/android/server/UiThread.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
import android.os.Trace;
@@ -26,20 +27,28 @@
* on it to avoid UI jank.
*/
public final class UiThread extends ServiceThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
private static UiThread sInstance;
private static Handler sHandler;
private UiThread() {
super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
+ }
+
+ @Override
+ public void run() {
// Make sure UiThread is in the fg stune boost group
Process.setThreadGroup(Process.myTid(), Process.THREAD_GROUP_TOP_APP);
+ super.run();
}
private static void ensureThreadLocked() {
if (sInstance == null) {
sInstance = new UiThread();
sInstance.start();
- sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ looper.setSlowDispatchThresholdMs(SLOW_DISPATCH_THRESHOLD_MS);
sHandler = new Handler(sInstance.getLooper());
}
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ce4ca02..80f89fc 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hidl.manager.V1_0.IServiceManager;
import android.os.Debug;
import android.os.Handler;
import android.os.IPowerManager;
@@ -42,6 +43,9 @@
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
/** This class calls its monitor every minute. Killing this process if they don't return **/
public class Watchdog extends Thread {
@@ -75,6 +79,14 @@
"com.android.bluetooth", // Bluetooth service
};
+ public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
+ "android.hardware.audio@2.0::IDevicesFactory",
+ "android.hardware.bluetooth@1.0::IBluetoothHci",
+ "android.hardware.camera.provider@2.4::ICameraProvider",
+ "android.hardware.vr@1.0::IVr",
+ "android.hardware.media.omx@1.0::IOmx"
+ );
+
static Watchdog sWatchdog;
/* This handler will be used to post message back onto the main thread */
@@ -344,6 +356,43 @@
return builder.toString();
}
+ private ArrayList<Integer> getInterestingHalPids() {
+ try {
+ IServiceManager serviceManager = IServiceManager.getService();
+ ArrayList<IServiceManager.InstanceDebugInfo> dump =
+ serviceManager.debugDump();
+ HashSet<Integer> pids = new HashSet<>();
+ for (IServiceManager.InstanceDebugInfo info : dump) {
+ if (info.pid == IServiceManager.PidConstant.NO_PID) {
+ continue;
+ }
+
+ if (!HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
+ continue;
+ }
+
+ pids.add(info.pid);
+ }
+ return new ArrayList<Integer>(pids);
+ } catch (RemoteException e) {
+ return new ArrayList<Integer>();
+ }
+ }
+
+ private ArrayList<Integer> getInterestingNativePids() {
+ ArrayList<Integer> pids = getInterestingHalPids();
+
+ int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
+ if (nativePids != null) {
+ pids.ensureCapacity(pids.size() + nativePids.length);
+ for (int i : nativePids) {
+ pids.add(i);
+ }
+ }
+
+ return pids;
+ }
+
@Override
public void run() {
boolean waitedHalf = false;
@@ -400,7 +449,7 @@
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
- NATIVE_STACKS_OF_INTEREST);
+ getInterestingNativePids());
waitedHalf = true;
}
continue;
@@ -417,13 +466,13 @@
// Then kill this process so that the system will restart.
EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
- ArrayList<Integer> pids = new ArrayList<Integer>();
+ ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
if (mPhonePid > 0) pids.add(mPhonePid);
// Pass !waitedHalf so that just in case we somehow wind up here without having
// dumped the halfway stacks, we properly re-initialize the trace file.
final File stack = ActivityManagerService.dumpStackTraces(
- !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
+ !waitedHalf, pids, null, null, getInterestingNativePids());
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a minute, another second or two won't hurt much.
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 3eb236e..8cb0eee 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;
@@ -3211,18 +3212,18 @@
}
}
- mWindowManager.setFocusedApp(r.appToken, true);
-
- applyUpdateLockStateLocked(r);
- applyUpdateVrModeLocked(r);
if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
mHandler.obtainMessage(
FOREGROUND_PROFILE_CHANGED_MSG, r.userId, 0).sendToTarget();
}
-
mLastResumedActivity = r;
+ mWindowManager.setFocusedApp(r.appToken, true);
+
+ applyUpdateLockStateLocked(r);
+ applyUpdateVrModeLocked(r);
+
EventLogTags.writeAmSetResumedActivity(
r == null ? -1 : r.userId,
r == null ? "NULL" : r.shortComponentName,
@@ -3928,8 +3929,13 @@
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
- if (new File(wrapperFileName).exists()) {
- invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ if (new File(wrapperFileName).exists()) {
+ invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
}
@@ -5468,11 +5474,12 @@
* appended to any existing file content.
* @param firstPids of dalvik VM processes to dump stack traces for first
* @param lastPids of dalvik VM processes to dump stack traces for last
- * @param nativeProcs optional list of native process names to dump stack crawls
+ * @param nativePids optional list of native pids to dump stack crawls
* @return file containing stack traces, or null if no dump file is configured
*/
public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+ ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+ ArrayList<Integer> nativePids) {
String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
if (tracesPath == null || tracesPath.length() == 0) {
return null;
@@ -5488,7 +5495,7 @@
return null;
}
- dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+ dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativePids);
return tracesFile;
}
@@ -5530,7 +5537,8 @@
}
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+ ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
+ ArrayList<Integer> nativePids) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
@@ -5551,18 +5559,15 @@
}
// Next collect the stacks of the native pids
- if (nativeProcs != null) {
- int[] pids = Process.getPidsForCommands(nativeProcs);
- if (pids != null) {
- for (int pid : pids) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
- final long sime = SystemClock.elapsedRealtime();
+ if (nativePids != null) {
+ for (int pid : nativePids) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
+ final long sime = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(
- pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
- if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
- + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
- }
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
+ if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+ + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
}
@@ -18277,6 +18282,10 @@
return record.info.isInstantApp();
}
// Otherwise check with PackageManager.
+ if (callerPackage == null) {
+ Slog.e(TAG, "isInstantApp with an application's uid, no record, and no package name");
+ throw new IllegalArgumentException("Calling application did not provide package name");
+ }
mAppOpsService.checkPackage(uid, callerPackage);
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -23590,6 +23599,21 @@
}
}
+ /**
+ * Return the user id of the last resumed activity.
+ */
+ @Override
+ public @UserIdInt int getLastResumedActivityUserId() {
+ enforceCallingPermission(
+ permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
+ synchronized (this) {
+ if (mLastResumedActivity == null) {
+ return mUserController.getCurrentUserIdLocked();
+ }
+ return mLastResumedActivity.userId;
+ }
+ }
+
private final class SleepTokenImpl extends SleepToken {
private final String mTag;
private final long mAcquireTime;
@@ -23876,6 +23900,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/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 04a09fe..5edfb06 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -10,8 +10,8 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -313,7 +313,7 @@
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.launchedActivity.packageName);
builder.setType(type);
- builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name);
+ builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
if (info.launchedActivity.launchedFromPackage != null) {
builder.addTaggedData(APP_TRANSITION_CALLING_PACKAGE_NAME,
info.launchedActivity.launchedFromPackage);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9a4f804..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;
@@ -759,6 +760,12 @@
}
}
+ final void removeActivitiesFromLRUListLocked(TaskRecord task) {
+ for (ActivityRecord r : task.mActivities) {
+ mLRUActivities.remove(r);
+ }
+ }
+
final boolean updateLRUListLocked(ActivityRecord r) {
final boolean hadit = mLRUActivities.remove(r);
mLRUActivities.add(r);
@@ -1179,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);
@@ -1207,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);
@@ -1237,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);
@@ -1268,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);
@@ -3482,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;
@@ -3523,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.
@@ -3535,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) {
@@ -3546,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);
}
@@ -3565,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;
@@ -3858,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();
@@ -4917,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.
@@ -4930,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();
}
@@ -4947,6 +4996,7 @@
}
}
mTaskHistory.remove(task);
+ removeActivitiesFromLRUListLocked(task);
updateTaskMovement(task, true);
if (mode == REMOVE_TASK_MODE_DESTROYING && task.mActivities.isEmpty()) {
@@ -5114,6 +5164,7 @@
// Apps may depend on onResume()/onPause() being called in pairs.
if (setResume) {
mResumedActivity = r;
+ updateLRUListLocked(r);
}
// If the activity was previously pausing, then ensure we transfer that as well
if (setPause) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 27b8e91..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);
@@ -3555,8 +3569,16 @@
stackHeader.append(" mFullscreen=" + stack.mFullscreen);
stackHeader.append("\n");
stackHeader.append(" mBounds=" + stack.mBounds);
- printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
- needSep, stackHeader.toString());
+
+ final boolean printedStackHeader = stack.dumpActivitiesLocked(fd, pw, dumpAll,
+ dumpClient, dumpPackage, needSep, stackHeader.toString());
+ printed |= printedStackHeader;
+ if (!printedStackHeader) {
+ // Ensure we always dump the stack header even if there are no activities
+ pw.println();
+ pw.println(stackHeader);
+ }
+
printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, " ", "Run", false,
!dumpAll, false, dumpPackage, true,
" Running activities (most recent first):", null);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 1b7b225..9258539 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -640,6 +640,18 @@
final Intent ephemeralIntent = new Intent(intent);
// Don't modify the client's object!
intent = new Intent(intent);
+ if (componentSpecified
+ && intent.getData() != null
+ && Intent.ACTION_VIEW.equals(intent.getAction())
+ && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+ && mService.getPackageManagerInternalLocked()
+ .isInstantAppInstallerComponent(intent.getComponent())) {
+ // intercept intents targeted directly to the ephemeral installer the
+ // ephemeral installer should never be started with a raw URL; instead
+ // adjust the intent so it looks like a "normal" instant app launch
+ intent.setComponent(null /*component*/);
+ componentSpecified = false;
+ }
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
if (rInfo == null) {
@@ -1453,6 +1465,12 @@
return intentActivity;
}
+ /**
+ * Figure out which task and activity to bring to front when we have found an existing matching
+ * activity record in history. May also clear the task if needed.
+ * @param intentActivity Existing matching activity.
+ * @return {@link ActivityRecord} brought to front.
+ */
private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
mTargetStack = intentActivity.getStack();
mTargetStack.mLastPausedActivity = null;
@@ -1514,6 +1532,14 @@
"bringToFrontInsteadOfAdjacentLaunch");
}
mMovedToFront = true;
+ } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
+ // Target and computed stacks are on different displays and we've
+ // found a matching task - move the existing instance to that display and
+ // move it to front.
+ intentActivity.task.reparent(launchStack.mStackId, ON_TOP,
+ REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+ "reparentToDisplay");
+ mMovedToFront = true;
}
mOptions = null;
@@ -1790,21 +1816,7 @@
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- if (mLaunchBounds != null) {
- mInTask.updateOverrideConfiguration(mLaunchBounds);
- int stackId = mInTask.getLaunchStackId();
- if (stackId != mInTask.getStackId()) {
- mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
- DEFER_RESUME, "inTaskToFront");
- stackId = mInTask.getStackId();
- }
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
- }
- }
mTargetStack = mInTask.getStack();
- mTargetStack.moveTaskToFrontLocked(
- mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
// Check whether we should actually launch the new activity in to the task,
// or just reuse the current activity on top.
@@ -1813,6 +1825,8 @@
&& top.userId == mStartActivity.userId) {
if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
@@ -1826,12 +1840,31 @@
}
if (!mAddingToTask) {
+ mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, "inTaskToFront");
// We don't actually want to have this activity added to the task, so just
// stop here but still tell the caller that we consumed the intent.
ActivityOptions.abort(mOptions);
return START_TASK_TO_FRONT;
}
+ if (mLaunchBounds != null) {
+ mInTask.updateOverrideConfiguration(mLaunchBounds);
+ int stackId = mInTask.getLaunchStackId();
+ if (stackId != mInTask.getStackId()) {
+ mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "inTaskToFront");
+ stackId = mInTask.getStackId();
+ mTargetStack = mInTask.getStack();
+ }
+ if (StackId.resizeStackWithLaunchBounds(stackId)) {
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+ }
+ }
+
+ mTargetStack.moveTaskToFrontLocked(
+ mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
+
addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+ " in explicit task " + mStartActivity.task);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index f927cce..c10f77c 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -870,12 +870,22 @@
nativeProcs = NATIVE_STACKS_OF_INTEREST;
}
+ int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
+ ArrayList<Integer> nativePids = null;
+
+ if (pids != null) {
+ nativePids = new ArrayList<Integer>(pids.length);
+ for (int i : pids) {
+ nativePids.add(i);
+ }
+ }
+
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
File tracesFile = mService.dumpStackTraces(true, firstPids,
(isSilentANR) ? null : processCpuTracker,
(isSilentANR) ? null : lastPids,
- nativeProcs);
+ nativePids);
String cpuInfo = null;
if (ActivityManagerService.MONITOR_CPU_USAGE) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index fd65c10..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;
@@ -617,15 +618,15 @@
boolean kept = true;
try {
final ActivityRecord r = topRunningActivityLocked();
- final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+ final boolean wasFocused = r != null && supervisor.isFocusedStack(sourceStack)
&& (topRunningActivityLocked() == r);
- final boolean wasResumed = sourceStack.mResumedActivity == r;
- final boolean wasPaused = sourceStack.mPausingActivity == r;
+ final boolean wasResumed = r != null && sourceStack.mResumedActivity == r;
+ final boolean wasPaused = r != null && sourceStack.mPausingActivity == r;
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
// Whenever we are moving the top activity from the front stack we want to make sure to
// move the stack to the front.
- final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+ final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
&& (sourceStack.topRunningActivityLocked() == r);
// Adjust the position for the new parent stack as needed.
@@ -667,8 +668,10 @@
// new stack by moving the stack to the front.
final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
|| (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
- toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
- reason);
+ if (r != null) {
+ toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed,
+ wasPaused, reason);
+ }
if (!animate) {
toStack.mNoAnimActivities.add(topActivity);
}
@@ -1278,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
@@ -1289,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);
@@ -1302,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;
}
@@ -1315,7 +1338,7 @@
*/
final void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked(0);
+ performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
mReuseTask = false;
}
@@ -1399,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 333d27b..49d1521 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -106,6 +106,7 @@
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
@@ -126,6 +127,7 @@
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -575,6 +577,10 @@
private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
private long mLoweredFromNormalToVibrateTime;
+ // Array of Uids of valid accessibility services to check if caller is one of them
+ private int[] mAccessibilityServiceUids;
+ private final Object mAccessibilityServiceUidsLock = new Object();
+
// Intent "extra" data keys.
public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
public static final String CONNECT_INTENT_KEY_STATE = "state";
@@ -1241,11 +1247,9 @@
/** @see AudioManager#adjustStreamVolume(int, int, int) */
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
- if ( streamType == AudioManager.STREAM_ACCESSIBILITY
- && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) {
+ if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
- + "BIND_ACCESSIBILITY_SERVICE / callingPackage=" + callingPackage);
+ + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
return;
}
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
@@ -1559,17 +1563,33 @@
/** @see AudioManager#setStreamVolume(int, int, int) */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
- if ( streamType == AudioManager.STREAM_ACCESSIBILITY
- && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) {
+ if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
- + " BIND_ACCESSIBILITY_SERVICE callingPackage=" + callingPackage);
+ + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage);
return;
}
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
}
+ private boolean canChangeAccessibilityVolume() {
+ synchronized (mAccessibilityServiceUidsLock) {
+ if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) {
+ return true;
+ }
+ if (mAccessibilityServiceUids != null) {
+ int callingUid = Binder.getCallingUid();
+ for (int i = 0; i < mAccessibilityServiceUids.length; i++) {
+ if (mAccessibilityServiceUids[i] == callingUid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
String caller, int uid) {
if (DEBUG_VOL) {
@@ -6380,6 +6400,29 @@
}
}
}
+
+ @Override
+ public void setAccessibilityServiceUids(IntArray uids) {
+ synchronized (mAccessibilityServiceUidsLock) {
+ if (uids.size() == 0) {
+ mAccessibilityServiceUids = null;
+ } else {
+ boolean changed = (mAccessibilityServiceUids == null)
+ || (mAccessibilityServiceUids.length != uids.size());
+ if (!changed) {
+ for (int i = 0; i < mAccessibilityServiceUids.length; i++) {
+ if (uids.get(i) != mAccessibilityServiceUids[i]) {
+ changed = true;
+ break;
+ }
+ }
+ }
+ if (changed) {
+ mAccessibilityServiceUids = uids.toArray();
+ }
+ }
+ }
+ }
}
//==========================================================================================
@@ -6555,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);
}
@@ -6566,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/connectivity/MockableSystemProperties.java b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
index 4f68652..77b86d8 100644
--- a/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
+++ b/services/core/java/com/android/server/connectivity/MockableSystemProperties.java
@@ -19,7 +19,20 @@
import android.os.SystemProperties;
public class MockableSystemProperties {
+
+ public String get(String key) {
+ return SystemProperties.get(key);
+ }
+
+ public int getInt(String key, int def) {
+ return SystemProperties.getInt(key, def);
+ }
+
public boolean getBoolean(String key, boolean def) {
return SystemProperties.getBoolean(key, def);
}
+
+ public void set(String key, String value) {
+ SystemProperties.set(key, value);
+ }
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index bbad493..1a27a39 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -907,7 +907,12 @@
Bundle finalExtras = new Bundle(extras);
String packageName = syncAdapterInfo.componentName.getPackageName();
// If the app did not run and has no account access, done
- if (!mPackageManagerInternal.wasPackageEverLaunched(packageName, userId)) {
+ try {
+ if (!mPackageManagerInternal.wasPackageEverLaunched(packageName, userId)) {
+ continue;
+ }
+ } catch (IllegalArgumentException e) {
+ // Package not found, race with an uninstall
continue;
}
mAccountManagerInternal.requestAccountAccess(account.account,
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..53a8092 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,
@@ -782,7 +772,12 @@
private final class SessionStub extends ISession.Stub {
@Override
public void destroy() {
- mService.destroySession(MediaSessionRecord.this);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mService.destroySession(MediaSessionRecord.this);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -817,6 +812,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 +843,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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index fc45344..b165d34 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,9 @@
import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO;
import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION;
import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.internal.util.ArrayUtils.appendInt;
@@ -141,6 +144,7 @@
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.MessageQueue.IdleHandler;
+import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
@@ -153,6 +157,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -174,12 +179,14 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.DeviceIdleController;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.power.BatterySaverPolicy.ServiceType;
@@ -204,6 +211,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Calendar;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -310,6 +318,9 @@
private static final int MSG_SET_FIREWALL_RULES = 14;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
+ private static final int UID_MSG_STATE_CHANGED = 100;
+ private static final int UID_MSG_GONE = 101;
+
private final Context mContext;
private final IActivityManager mActivityManager;
private final INetworkStatsService mNetworkStats;
@@ -317,6 +328,7 @@
private UsageStatsManagerInternal mUsageStats;
private final TrustedTime mTime;
private final UserManager mUserManager;
+ private final CarrierConfigManager mCarrierConfigManager;
private IConnectivityManager mConnManager;
private INotificationManager mNotifManager;
@@ -420,6 +432,9 @@
mListeners = new RemoteCallbackList<>();
final Handler mHandler;
+ final Handler mUidEventHandler;
+
+ private final ServiceThread mUidEventThread;
@GuardedBy("allLocks")
private final AtomicFile mPolicyFile;
@@ -465,12 +480,19 @@
Context.DEVICE_IDLE_CONTROLLER));
mTime = checkNotNull(time, "missing TrustedTime");
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
mIPm = pm;
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new Handler(thread.getLooper(), mHandlerCallback);
+ // We create another thread for the UID events, which are more time-critical.
+ mUidEventThread = new ServiceThread(TAG + ".uid", Process.THREAD_PRIORITY_FOREGROUND,
+ /*allowIo=*/ false);
+ mUidEventThread.start();
+ mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback);
+
mSuppressDefaultPolicy = suppressDefaultPolicy;
mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
@@ -743,6 +765,11 @@
WifiManager.NETWORK_STATE_CHANGED_ACTION);
mContext.registerReceiver(mWifiStateReceiver, wifiStateFilter, null, mHandler);
+ // listen for carrier config changes to update data cycle information
+ final IntentFilter carrierConfigFilter = new IntentFilter(
+ ACTION_CARRIER_CONFIG_CHANGED);
+ mContext.registerReceiver(mCarrierConfigReceiver, carrierConfigFilter, null, mHandler);
+
mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
// tell systemReady() that the service has been initialized
initCompleteSignal.countDown();
@@ -774,26 +801,12 @@
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState,
long procStateSeq) throws RemoteException {
- Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
- try {
- synchronized (mUidRulesFirstLock) {
- // We received a uid state change callback, add it to the history so that it
- // will be useful for debugging.
- mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
- // Now update the network policy rules as per the updated uid state.
- updateUidStateUL(uid, procState);
- // Updating the network rules is done, so notify AMS about this.
- mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
- }
+ mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED,
+ uid, procState, procStateSeq).sendToTarget();
}
@Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
- synchronized (mUidRulesFirstLock) {
- removeUidStateUL(uid);
- }
+ mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
}
@Override public void onUidActive(int uid) throws RemoteException {
@@ -1293,6 +1306,213 @@
};
/**
+ * Update mobile policies with data cycle information from {@link CarrierConfigManager}
+ * if necessary.
+ *
+ * @param subId that has its associated NetworkPolicy updated if necessary
+ * @return if any policies were updated
+ */
+ private boolean maybeUpdateMobilePolicyCycleNL(int subId) {
+ if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleNL()");
+ final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+
+ if (config == null) {
+ return false;
+ }
+
+ boolean policyUpdated = false;
+ final String subscriberId = TelephonyManager.from(mContext).getSubscriberId(subId);
+
+ // find and update the mobile NetworkPolicy for this subscriber id
+ final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true);
+ for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
+ final NetworkTemplate template = mNetworkPolicy.keyAt(i);
+ if (template.matches(probeIdent)) {
+ NetworkPolicy policy = mNetworkPolicy.valueAt(i);
+
+ // only update the policy if the user didn't change any of the defaults.
+ if (!policy.inferred) {
+ // TODO: inferred could be split, so that if a user changes their data limit or
+ // warning, it doesn't prevent their cycle date from being updated.
+ if (LOGD) Slog.v(TAG, "Didn't update NetworkPolicy because policy.inferred");
+ continue;
+ }
+
+ final int cycleDay = getCycleDayFromCarrierConfig(config, policy.cycleDay);
+ final long warningBytes = getWarningBytesFromCarrierConfig(config,
+ policy.warningBytes);
+ final long limitBytes = getLimitBytesFromCarrierConfig(config,
+ policy.limitBytes);
+
+ if (policy.cycleDay == cycleDay &&
+ policy.warningBytes == warningBytes &&
+ policy.limitBytes == limitBytes) {
+ continue;
+ }
+
+ policyUpdated = true;
+ policy.cycleDay = cycleDay;
+ policy.warningBytes = warningBytes;
+ policy.limitBytes = limitBytes;
+
+ if (LOGD) {
+ Slog.d(TAG, "Updated NetworkPolicy " + policy + " which matches subscriber "
+ + NetworkIdentity.scrubSubscriberId(subscriberId)
+ + " from CarrierConfigManager");
+ }
+ }
+ }
+
+ return policyUpdated;
+ }
+
+ /**
+ * Returns the cycle day that should be used for a mobile NetworkPolicy.
+ *
+ * It attempts to get an appropriate cycle day from the passed in CarrierConfig. If it's unable
+ * to do so, it returns the fallback value.
+ *
+ * @param config The CarrierConfig to read the value from.
+ * @param fallbackCycleDay to return if the CarrierConfig can't be read.
+ * @return cycleDay to use in the mobile NetworkPolicy.
+ */
+ @VisibleForTesting
+ public int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config,
+ int fallbackCycleDay) {
+ if (config == null) {
+ return fallbackCycleDay;
+ }
+ int cycleDay =
+ config.getInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT);
+ if (cycleDay == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+ return fallbackCycleDay;
+ }
+ // validate cycleDay value
+ final Calendar cal = Calendar.getInstance();
+ if (cycleDay < cal.getMinimum(Calendar.DAY_OF_MONTH) ||
+ cycleDay > cal.getMaximum(Calendar.DAY_OF_MONTH)) {
+ Slog.e(TAG, "Invalid date in "
+ + "CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT: " + cycleDay);
+ return fallbackCycleDay;
+ }
+ return cycleDay;
+ }
+
+ /**
+ * Returns the warning bytes that should be used for a mobile NetworkPolicy.
+ *
+ * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
+ * to do so, it returns the fallback value.
+ *
+ * @param config The CarrierConfig to read the value from.
+ * @param fallbackWarningBytes to return if the CarrierConfig can't be read.
+ * @return warningBytes to use in the mobile NetworkPolicy.
+ */
+ @VisibleForTesting
+ public long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config,
+ long fallbackWarningBytes) {
+ if (config == null) {
+ return fallbackWarningBytes;
+ }
+ long warningBytes =
+ config.getLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG);
+
+ if (warningBytes == DATA_CYCLE_THRESHOLD_DISABLED) {
+ return WARNING_DISABLED;
+ } else if (warningBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+ return getPlatformDefaultWarningBytes();
+ } else if (warningBytes < 0) {
+ Slog.e(TAG, "Invalid value in "
+ + "CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG; expected a "
+ + "non-negative value but got: " + warningBytes);
+ return fallbackWarningBytes;
+ }
+
+ return warningBytes;
+ }
+
+ /**
+ * Returns the limit bytes that should be used for a mobile NetworkPolicy.
+ *
+ * It attempts to get an appropriate value from the passed in CarrierConfig. If it's unable
+ * to do so, it returns the fallback value.
+ *
+ * @param config The CarrierConfig to read the value from.
+ * @param fallbackLimitBytes to return if the CarrierConfig can't be read.
+ * @return limitBytes to use in the mobile NetworkPolicy.
+ */
+ @VisibleForTesting
+ public long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config,
+ long fallbackLimitBytes) {
+ if (config == null) {
+ return fallbackLimitBytes;
+ }
+ long limitBytes =
+ config.getLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG);
+
+ if (limitBytes == DATA_CYCLE_THRESHOLD_DISABLED) {
+ return LIMIT_DISABLED;
+ } else if (limitBytes == DATA_CYCLE_USE_PLATFORM_DEFAULT) {
+ return getPlatformDefaultLimitBytes();
+ } else if (limitBytes < 0) {
+ Slog.e(TAG, "Invalid value in "
+ + "CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG; expected a "
+ + "non-negative value but got: " + limitBytes);
+ return fallbackLimitBytes;
+ }
+ return limitBytes;
+ }
+
+ /**
+ * Receiver that watches for {@link CarrierConfigManager} to be changed.
+ */
+ private BroadcastReceiver mCarrierConfigReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // No need to do a permission check, because the ACTION_CARRIER_CONFIG_CHANGED
+ // broadcast is protected and can't be spoofed. Runs on a background handler thread.
+
+ if (!intent.hasExtra(PhoneConstants.SUBSCRIPTION_KEY)) {
+ return;
+ }
+ final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, -1);
+ final TelephonyManager tele = TelephonyManager.from(mContext);
+ final String subscriberId = tele.getSubscriberId(subId);
+
+ maybeRefreshTrustedTime();
+ synchronized (mUidRulesFirstLock) {
+ synchronized (mNetworkPoliciesSecondLock) {
+ final boolean added = ensureActiveMobilePolicyNL(subId, subscriberId);
+ if (added) return;
+ final boolean updated = maybeUpdateMobilePolicyCycleNL(subId);
+ if (!updated) return;
+ // update network and notification rules, as the data cycle changed and it's
+ // possible that we should be triggering warnings/limits now
+ handleNetworkPoliciesUpdateAL(true);
+ }
+ }
+ }
+ };
+
+ /**
+ * Handles all tasks that need to be run after a new network policy has been set, or an existing
+ * one has been updated.
+ *
+ * @param shouldNormalizePolicies true iff network policies need to be normalized after the
+ * update.
+ */
+ void handleNetworkPoliciesUpdateAL(boolean shouldNormalizePolicies) {
+ if (shouldNormalizePolicies) {
+ normalizePoliciesNL();
+ }
+ updateNetworkEnabledNL();
+ updateNetworkRulesNL();
+ updateNotificationsNL();
+ writePolicyAL();
+ }
+
+ /**
* Proactively control network data connections when they exceed
* {@link NetworkPolicy#limitBytes}.
*/
@@ -1517,11 +1737,19 @@
final int[] subIds = sub.getActiveSubscriptionIdList();
for (int subId : subIds) {
final String subscriberId = tele.getSubscriberId(subId);
- ensureActiveMobilePolicyNL(subscriberId);
+ ensureActiveMobilePolicyNL(subId, subscriberId);
}
}
- private void ensureActiveMobilePolicyNL(String subscriberId) {
+ /**
+ * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
+ * have at least a default mobile policy defined.
+ *
+ * @param subId to build a default policy for
+ * @param subscriberId that we check for an existing policy
+ * @return true if a mobile network policy was added, or false one already existed.
+ */
+ private boolean ensureActiveMobilePolicyNL(int subId, String subscriberId) {
// Poke around to see if we already have a policy
final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true);
@@ -1532,33 +1760,51 @@
Slog.d(TAG, "Found template " + template + " which matches subscriber "
+ NetworkIdentity.scrubSubscriberId(subscriberId));
}
- return;
+ return false;
}
}
Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)
+ "; generating default policy");
+ final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId);
+ addNetworkPolicyNL(policy);
+ return true;
+ }
- // Build default mobile policy, and assume usage cycle starts today
+ private long getPlatformDefaultWarningBytes() {
final int dataWarningConfig = mContext.getResources().getInteger(
com.android.internal.R.integer.config_networkPolicyDefaultWarning);
- final long warningBytes;
if (dataWarningConfig == WARNING_DISABLED) {
- warningBytes = WARNING_DISABLED;
+ return WARNING_DISABLED;
} else {
- warningBytes = dataWarningConfig * MB_IN_BYTES;
+ return dataWarningConfig * MB_IN_BYTES;
}
+ }
+ private long getPlatformDefaultLimitBytes() {
+ return LIMIT_DISABLED;
+ }
+
+ @VisibleForTesting
+ public NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
+ PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+
+ // assume usage cycle starts today
final Time time = new Time();
time.setToNow();
- final int cycleDay = time.monthDay;
final String cycleTimezone = time.timezone;
+ final int cycleDay = getCycleDayFromCarrierConfig(config, time.monthDay);
+ final long warningBytes = getWarningBytesFromCarrierConfig(config,
+ getPlatformDefaultWarningBytes());
+ final long limitBytes = getLimitBytesFromCarrierConfig(config,
+ getPlatformDefaultLimitBytes());
+
final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
- warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
- addNetworkPolicyNL(policy);
+ warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
+ return policy;
}
private void readPolicyAL() {
@@ -2026,10 +2272,7 @@
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
normalizePoliciesNL(policies);
- updateNetworkEnabledNL();
- updateNetworkRulesNL();
- updateNotificationsNL();
- writePolicyAL();
+ handleNetworkPoliciesUpdateAL(false);
}
}
} finally {
@@ -2126,11 +2369,7 @@
throw new IllegalArgumentException("unexpected type");
}
- normalizePoliciesNL();
- updateNetworkEnabledNL();
- updateNetworkRulesNL();
- updateNotificationsNL();
- writePolicyAL();
+ handleNetworkPoliciesUpdateAL(true);
}
}
}
@@ -2361,11 +2600,7 @@
mNetworkPolicy.valueAt(i).clearSnooze();
}
- normalizePoliciesNL();
- updateNetworkEnabledNL();
- updateNetworkRulesNL();
- updateNotificationsNL();
- writePolicyAL();
+ handleNetworkPoliciesUpdateAL(true);
fout.println("Cleared snooze timestamps");
return;
@@ -3317,7 +3552,7 @@
}
}
- private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
@@ -3441,9 +3676,61 @@
}
}
}
+ };
+
+ private final Handler.Callback mUidEventHandlerCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case UID_MSG_STATE_CHANGED: {
+ final int uid = msg.arg1;
+ final int procState = msg.arg2;
+ final long procStateSeq = (Long) msg.obj;
+
+ handleUidChanged(uid, procState, procStateSeq);
+ return true;
+ }
+ case UID_MSG_GONE: {
+ final int uid = msg.arg1;
+ handleUidGone(uid);
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
};
+ void handleUidChanged(int uid, int procState, long procStateSeq) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
+ try {
+ synchronized (mUidRulesFirstLock) {
+ // We received a uid state change callback, add it to the history so that it
+ // will be useful for debugging.
+ mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ // Now update the network policy rules as per the updated uid state.
+ updateUidStateUL(uid, procState);
+ // Updating the network rules is done, so notify AMS about this.
+ mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ void handleUidGone(int uid) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidGone");
+ try {
+ synchronized (mUidRulesFirstLock) {
+ removeUidStateUL(uid);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
private void broadcastRestrictBackgroundChanged(int uid, Boolean changed) {
final PackageManager pm = mContext.getPackageManager();
final String[] packages = pm.getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8cc9375..621e37b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -842,6 +842,8 @@
/** Component used to install ephemeral applications */
ComponentName mInstantAppInstallerComponent;
+ /** Component used to show resolver settings for Instant Apps */
+ ComponentName mInstantAppResolverSettingsComponent;
ActivityInfo mInstantAppInstallerActivity;
final ResolveInfo mInstantAppInstallerInfo = new ResolveInfo();
@@ -2890,6 +2892,7 @@
mInstantAppResolverConnection = null;
}
updateInstantAppInstallerLocked();
+ mInstantAppResolverSettingsComponent = getEphemeralResolverSettingsLPr();
// Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
@@ -3196,6 +3199,37 @@
}
}
+ private @Nullable ComponentName getEphemeralResolverSettingsLPr() {
+ final Intent intent = new Intent(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ final int resolveFlags =
+ MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE
+ | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0);
+ final List<ResolveInfo> matches = queryIntentActivitiesInternal(intent, null,
+ resolveFlags, UserHandle.USER_SYSTEM);
+ Iterator<ResolveInfo> iter = matches.iterator();
+ while (iter.hasNext()) {
+ final ResolveInfo rInfo = iter.next();
+ final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName);
+ if (ps != null) {
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ if (permissionsState.hasPermission(Manifest.permission.ACCESS_INSTANT_APPS, 0)) {
+ continue;
+ }
+ }
+ iter.remove();
+ }
+ if (matches.size() == 0) {
+ return null;
+ } else if (matches.size() == 1) {
+ return matches.get(0).getComponentInfo().getComponentName();
+ } else {
+ throw new RuntimeException(
+ "There must be at most one ephemeral resolver settings; found " + matches);
+ }
+ }
+
private void primeDomainVerificationsLPw(int userId) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -5788,6 +5822,10 @@
return false;
}
if (ps.getInstantApp(userId)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "DENY instant app installed;"
+ + " pkg: " + packageName);
+ }
return false;
}
}
@@ -6267,7 +6305,6 @@
intent, resolvedType, flags, userId), userId);
addEphemeral = !ephemeralDisabled
&& isEphemeralAllowed(intent, result, userId, false /*skipPackageCheck*/);
-
// Check for cross profile results.
boolean hasNonNegativePriorityResult = hasNonNegativePriority(result);
xpResolveInfo = queryCrossProfileIntents(
@@ -6323,7 +6360,7 @@
} else {
// the caller wants to resolve for a particular package; however, there
// were no installed results, so, try to find an ephemeral result
- addEphemeral = !ephemeralDisabled
+ addEphemeral = !ephemeralDisabled
&& isEphemeralAllowed(
intent, null /*result*/, userId, true /*skipPackageCheck*/);
result = new ArrayList<ResolveInfo>();
@@ -23073,6 +23110,13 @@
}
@Override
+ public boolean isInstantAppInstallerComponent(ComponentName component) {
+ synchronized (mPackages) {
+ return component != null && component.equals(mInstantAppInstallerComponent);
+ }
+ }
+
+ @Override
public void pruneInstantApps() {
synchronized (mPackages) {
mInstantAppRegistry.pruneInstantAppsLPw();
@@ -23313,4 +23357,9 @@
}
return checkUidPermission(appOpPermission, uid) == PERMISSION_GRANTED;
}
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ return mInstantAppResolverSettingsComponent;
+ }
}
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 904d915..6a0b648 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -21,7 +21,9 @@
import android.system.Os;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.util.Preconditions;
+import com.android.server.NativeDaemonConnectorException;
import libcore.io.IoUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -54,17 +56,17 @@
}
public ParcelFileDescriptor addBridge(MountScope mountScope)
- throws BridgeException {
+ throws FuseUnavailableMountException, NativeDaemonConnectorException {
try {
synchronized (this) {
Preconditions.checkArgument(mScopes.indexOfKey(mountScope.mountId) < 0);
if (mNativeLoop == 0) {
- throw new BridgeException("The thread has already been terminated");
+ throw new FuseUnavailableMountException(mountScope.mountId);
}
final int fd = native_add_bridge(
- mNativeLoop, mountScope.mountId, mountScope.deviceFd.detachFd());
+ mNativeLoop, mountScope.mountId, mountScope.open().detachFd());
if (fd == -1) {
- throw new BridgeException("Failed to invoke native_add_bridge");
+ throw new FuseUnavailableMountException(mountScope.mountId);
}
final ParcelFileDescriptor result = ParcelFileDescriptor.adoptFd(fd);
mScopes.put(mountScope.mountId, mountScope);
@@ -86,12 +88,12 @@
}
public ParcelFileDescriptor openFile(int pid, int mountId, int fileId, int mode)
- throws FileNotFoundException, SecurityException, InterruptedException {
+ throws FuseUnavailableMountException, InterruptedException {
final MountScope scope;
synchronized (this) {
scope = mScopes.get(mountId);
if (scope == null) {
- throw new FileNotFoundException("Cannot find mount point");
+ throw new FuseUnavailableMountException(mountId);
}
}
if (scope.pid != pid) {
@@ -99,17 +101,14 @@
}
final boolean result = scope.waitForMount();
if (result == false) {
- throw new FileNotFoundException("Mount failed");
+ throw new FuseUnavailableMountException(mountId);
}
try {
- if (Os.stat(scope.mountPoint.getPath()).st_ino != 1) {
- throw new FileNotFoundException("Could not find bridge mount point.");
- }
- } catch (ErrnoException e) {
- throw new FileNotFoundException(
- "Failed to stat mount point: " + scope.mountPoint.getParent());
+ return ParcelFileDescriptor.open(
+ new File(scope.mountPoint, String.valueOf(fileId)), mode);
+ } catch (FileNotFoundException error) {
+ throw new FuseUnavailableMountException(mountId);
}
- return ParcelFileDescriptor.open(new File(scope.mountPoint, String.valueOf(fileId)), mode);
}
// Used by com_android_server_storage_AppFuse.cpp.
@@ -130,20 +129,18 @@
}
}
- public static class MountScope implements AutoCloseable {
+ public static abstract class MountScope implements AutoCloseable {
public final int uid;
public final int pid;
public final int mountId;
- public final ParcelFileDescriptor deviceFd;
public final File mountPoint;
private final CountDownLatch mMounted = new CountDownLatch(1);
private boolean mMountResult = false;
- public MountScope(int uid, int pid, int mountId, ParcelFileDescriptor deviceFd) {
+ public MountScope(int uid, int pid, int mountId) {
this.uid = uid;
this.pid = pid;
this.mountId = mountId;
- this.deviceFd = deviceFd;
this.mountPoint = new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
}
@@ -161,16 +158,7 @@
return mMountResult;
}
- @Override
- public void close() throws Exception {
- deviceFd.close();
- }
- }
-
- public static class BridgeException extends Exception {
- public BridgeException(String message) {
- super(message);
- }
+ public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException;
}
private native long native_new();
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8f391a7..aa85574 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -175,6 +175,7 @@
private boolean mTmpRecoveringMemory;
private boolean mUpdateImeTarget;
private boolean mTmpInitial;
+ private int mMaxUiWidth;
// Mapping from a token IBinder to a WindowToken object on this display.
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@@ -1559,6 +1560,39 @@
}
}
+ /** Sets the maximum width the screen resolution can be */
+ void setMaxUiWidth(int width) {
+ if (DEBUG_DISPLAY) {
+ Slog.v(TAG_WM, "Setting max ui width:" + width + " on display:" + getDisplayId());
+ }
+
+ mMaxUiWidth = width;
+
+ // Update existing metrics.
+ updateBaseDisplayMetrics(mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+ }
+
+ /** Update base (override) display metrics. */
+ void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+ mBaseDisplayWidth = baseWidth;
+ mBaseDisplayHeight = baseHeight;
+ mBaseDisplayDensity = baseDensity;
+
+ if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) {
+ mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth;
+ mBaseDisplayDensity = (mMaxUiWidth * mBaseDisplayDensity) / mBaseDisplayWidth;
+ mBaseDisplayWidth = mMaxUiWidth;
+
+ if (DEBUG_DISPLAY) {
+ Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x"
+ + mBaseDisplayHeight + " at density:" + mBaseDisplayDensity
+ + " on display:" + getDisplayId());
+ }
+ }
+
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ }
+
void getContentRect(Rect out) {
out.set(mContentRect);
}
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/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fa9c2a7..0dc74d7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -385,6 +385,7 @@
final boolean mAllowBootMessages;
final boolean mLimitedAlphaCompositing;
+ final int mMaxUiWidth;
final WindowManagerPolicy mPolicy;
@@ -949,6 +950,8 @@
com.android.internal.R.integer.config_drawLockTimeoutMillis);
mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
+ mMaxUiWidth = context.getResources().getInteger(
+ com.android.internal.R.integer.config_maxUiWidth);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
@@ -4572,6 +4575,9 @@
synchronized(mWindowMap) {
final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ if (mMaxUiWidth > 0) {
+ displayContent.setMaxUiWidth(mMaxUiWidth);
+ }
readForcedDisplayPropertiesLocked(displayContent);
mDisplayReady = true;
}
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/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 9b4de043..70c7e58 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -26,6 +26,7 @@
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
+import android.util.LongSparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -44,12 +45,21 @@
// If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc
private static final int MAX_EVENTS_PER_BATCH = 1200;
- private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90);
- private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS =
- TimeUnit.MINUTES.toMillis(30);
+
+ /**
+ * Maximum number of batches to store in memory. If more batches are generated and the DO
+ * doesn't fetch them, we will discard the oldest one.
+ */
+ private static final int MAX_BATCHES = 5;
+
+ private static final long BATCH_FINALIZATION_TIMEOUT_MS = 90 * 60 * 1000; // 1.5h
+ private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = 30 * 60 * 1000; // 30m
private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout";
+ /** Delay after which older batches get discarded after a retrieval. */
+ private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m
+
private final DevicePolicyManagerService mDpm;
private final AlarmManager mAlarmManager;
@@ -66,22 +76,27 @@
static final int LOG_NETWORK_EVENT_MSG = 1;
- // threadsafe as it's Handler's thread confined
+ /** Network events accumulated so far to be finalized into a batch at some point. */
@GuardedBy("this")
- private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>();
+ private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+ /**
+ * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already
+ * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}.
+ */
@GuardedBy("this")
- private ArrayList<NetworkEvent> mFullBatch;
+ private final LongSparseArray<ArrayList<NetworkEvent>> mBatches =
+ new LongSparseArray<>(MAX_BATCHES);
@GuardedBy("this")
private boolean mPaused = false;
// each full batch is represented by its token, which the DPC has to provide back to retrieve it
@GuardedBy("this")
- private long mCurrentFullBatchToken;
+ private long mCurrentBatchToken;
@GuardedBy("this")
- private long mLastRetrievedFullBatchToken;
+ private long mLastRetrievedBatchToken;
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
super(looper);
@@ -93,7 +108,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case LOG_NETWORK_EVENT_MSG: {
- NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
+ final NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
if (networkEvent != null) {
synchronized (NetworkLoggingHandler.this) {
mNetworkEvents.add(networkEvent);
@@ -113,6 +128,8 @@
void scheduleBatchFinalization() {
final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS;
+ // We use alarm manager and not just postDelayed here to ensure the batch gets finalized
+ // even if the device goes to sleep.
mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG,
mBatchTimeoutAlarmListener, this);
@@ -131,62 +148,80 @@
return;
}
- Log.d(TAG, "Resumed network logging. Current batch="
- + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken);
+ Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentBatchToken
+ + ", LastRetrievedBatch=" + mLastRetrievedBatchToken);
mPaused = false;
- // If there is a full batch ready that the device owner hasn't been notified about, do it
- // now.
- if (mFullBatch != null && mFullBatch.size() > 0
- && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) {
+ // If there is a batch ready that the device owner hasn't been notified about, do it now.
+ if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) {
scheduleBatchFinalization();
notifyDeviceOwnerLocked();
}
}
synchronized void discardLogs() {
- mFullBatch = null;
- mNetworkEvents = new ArrayList<NetworkEvent>();
+ mBatches.clear();
+ mNetworkEvents = new ArrayList<>();
Log.d(TAG, "Discarded all network logs");
}
@GuardedBy("this")
private void finalizeBatchAndNotifyDeviceOwnerLocked() {
if (mNetworkEvents.size() > 0) {
- // finalize the batch and start a new one from scratch
- mFullBatch = mNetworkEvents;
- mCurrentFullBatchToken++;
- mNetworkEvents = new ArrayList<NetworkEvent>();
+ // Finalize the batch and start a new one from scratch.
+ if (mBatches.size() >= MAX_BATCHES) {
+ // Remove the oldest batch if we hit the limit.
+ mBatches.removeAt(0);
+ }
+ mCurrentBatchToken++;
+ mBatches.append(mCurrentBatchToken, mNetworkEvents);
+ mNetworkEvents = new ArrayList<>();
if (!mPaused) {
notifyDeviceOwnerLocked();
}
} else {
- // don't notify the DO, since there are no events; DPC can still retrieve
+ // Don't notify the DO, since there are no events; DPC can still retrieve
// the last full batch if not paused.
Log.d(TAG, "Was about to finalize the batch, but there were no events to send to"
- + " the DPC, the batchToken of last available batch: "
- + mCurrentFullBatchToken);
+ + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken);
}
- // regardless of whether the batch was non-empty schedule a new finalization after timeout
+ // Regardless of whether the batch was non-empty schedule a new finalization after timeout.
scheduleBatchFinalization();
}
+ /** Sends a notification to the DO. Should only be called when there is a batch available. */
@GuardedBy("this")
private void notifyDeviceOwnerLocked() {
- Bundle extras = new Bundle();
- extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
- extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+ final Bundle extras = new Bundle();
+ final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size();
+ extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken);
+ extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, lastBatchSize);
Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
- + mCurrentFullBatchToken);
+ + mCurrentBatchToken);
mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
}
- synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
- if (batchToken != mCurrentFullBatchToken) {
+ synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) {
+ final int index = mBatches.indexOfKey(batchToken);
+ if (index < 0) {
+ // Invalid token or batch has already been discarded.
return null;
}
- mLastRetrievedFullBatchToken = mCurrentFullBatchToken;
- return mFullBatch;
+
+ // Schedule this and older batches to be discarded after a delay to lessen memory load
+ // without interfering with the admin's ability to collect logs out-of-order.
+ // It isn't critical and we allow it to be delayed further if the phone sleeps, so we don't
+ // use the alarm manager here.
+ postDelayed(() -> {
+ synchronized(this) {
+ while (mBatches.size() > 0 && mBatches.keyAt(0) <= batchToken) {
+ mBatches.removeAt(0);
+ }
+ }
+ }, RETRIEVED_BATCH_DISCARD_DELAY_MS);
+
+ mLastRetrievedBatchToken = batchToken;
+ return mBatches.valueAt(index);
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f0732dd..da49eb3 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -317,7 +317,7 @@
// In case the runtime switched since last boot (such as when
// the old runtime was removed in an OTA), set the system
- // property so that it is in sync. We can't do this in
+ // property so that it is in sync. We can | xq oqi't do this in
// libnativehelper's JniInvocation::Init code where we already
// had to fallback to a different runtime because it is
// running as root and we need to be the system user to set
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/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index 9356dac..7790698 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -20,6 +20,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
+import android.annotation.CheckResult;
import android.annotation.Nullable;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -36,8 +37,11 @@
import android.net.NetworkPolicyManager;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdleController;
+import android.os.IInterface;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -68,11 +72,13 @@
import java.util.function.Function;
//TODO move to own package!
-//TODO un/linkToDeath & onBinderDied - unbind
//TODO onStop schedule unbind in 5 seconds
-//TODO Prune association on app uninstall
+//TODO make sure APIs are only callable from currently focused app
+//TODO schedule stopScan on activity destroy(except if configuration change)
+//TODO on associate called again after configuration change -> replace old callback with new
+//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
/** @hide */
-public class CompanionDeviceManagerService extends SystemService {
+public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
@@ -90,6 +96,8 @@
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private IDeviceIdleController mIdleController;
+ private IFindDeviceCallback mFindDeviceCallback;
+ private ServiceConnection mServiceConnection;
public CompanionDeviceManagerService(Context context) {
super(context);
@@ -125,7 +133,51 @@
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
}
+ @Override
+ public void binderDied() {
+ Handler.getMain().post(this::handleBinderDied);
+ }
+
+ private void handleBinderDied() {
+ mServiceConnection = unbind(mServiceConnection);
+ mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
+ }
+
+ /**
+ * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); }
+ */
+ @Nullable
+ @CheckResult
+ private static <T extends IInterface> T unlinkToDeath(T iinterface,
+ IBinder.DeathRecipient deathRecipient, int flags) {
+ if (iinterface != null) {
+ iinterface.asBinder().unlinkToDeath(deathRecipient, flags);
+ }
+ return null;
+ }
+
+ @Nullable
+ @CheckResult
+ private ServiceConnection unbind(@Nullable ServiceConnection conn) {
+ if (conn != null) {
+ getContext().unbindService(conn);
+ }
+ return null;
+ }
+
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (Throwable e) {
+ Slog.e(LOG_TAG, "Error during IPC", e);
+ throw ExceptionUtils.propagate(e, RemoteException.class);
+ }
+ }
+
@Override
public void associate(
AssociationRequest request,
@@ -135,14 +187,14 @@
Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+ ", callingPackage = " + callingPackage + ")");
}
- checkNotNull(request);
- checkNotNull(callback);
+ checkNotNull(request, "Request cannot be null");
+ checkNotNull(callback, "Callback cannot be null");
final long callingIdentity = Binder.clearCallingIdentity();
try {
//TODO bindServiceAsUser
getContext().bindService(
new Intent().setComponent(SERVICE_TO_BIND_TO),
- getServiceConnection(request, callback, callingPackage),
+ createServiceConnection(request, callback, callingPackage),
Context.BIND_AUTO_CREATE);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
@@ -168,11 +220,11 @@
return UserHandle.getUserId(Binder.getCallingUid());
}
- private ServiceConnection getServiceConnection(
+ private ServiceConnection createServiceConnection(
final AssociationRequest request,
final IFindDeviceCallback findDeviceCallback,
final String callingPackage) {
- return new ServiceConnection() {
+ mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
@@ -180,6 +232,14 @@
"onServiceConnected(name = " + name + ", service = "
+ service + ")");
}
+ mFindDeviceCallback = findDeviceCallback;
+ try {
+ mFindDeviceCallback.asBinder().linkToDeath(
+ CompanionDeviceManagerService.this, 0);
+ } catch (RemoteException e) {
+ handleBinderDied();
+ return;
+ }
try {
ICompanionDeviceDiscoveryService.Stub
.asInterface(service)
@@ -198,6 +258,7 @@
if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
}
};
+ return mServiceConnection;
}
private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
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/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 29c6f89..dbba727 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -26,8 +27,15 @@
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
import static android.net.NetworkPolicyManager.uidPoliciesToString;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.KB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
+import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
+import static android.telephony.CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG;
+import static android.telephony.CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG;
+import static android.telephony.CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
@@ -94,18 +102,24 @@
import android.net.NetworkTemplate;
import android.os.Binder;
import android.os.INetworkManagementService;
+import android.os.PersistableBundle;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.TrustedTime;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -143,6 +157,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -200,6 +215,9 @@
private @Mock INotificationManager mNotifManager;
private @Mock PackageManager mPackageManager;
private @Mock IPackageManager mIpm;
+ private @Mock SubscriptionManager mSubscriptionManager;
+ private @Mock CarrierConfigManager mCarrierConfigManager;
+ private @Mock TelephonyManager mTelephonyManager;
private static ActivityManagerInternal mActivityManagerInternal;
@@ -214,6 +232,12 @@
private long mElapsedRealtime;
private static final int USER_ID = 0;
+ private static final int FAKE_SUB_ID = 3737373;
+ private static final String FAKE_SUBSCRIBER_ID = "FAKE_SUB_ID";
+ private static final int DEFAULT_CYCLE_DAY = 1;
+ private static final int INVALID_CARRIER_CONFIG_VALUE = -9999;
+ private long mDefaultWarningBytes; // filled in with the actual default before tests are run
+ private long mDefaultLimitBytes; // filled in with the actual default before tests are run
private static final int APP_ID_A = android.os.Process.FIRST_APPLICATION_UID + 4;
private static final int APP_ID_B = android.os.Process.FIRST_APPLICATION_UID + 8;
@@ -235,6 +259,8 @@
@BeforeClass
public static void registerLocalServices() {
+ final PowerManagerInternal powerManager = addLocalServiceMock(PowerManagerInternal.class);
+ when(powerManager.getLowPowerState(anyInt())).thenReturn(mock(PowerSaveState.class));
addLocalServiceMock(DeviceIdleController.LocalService.class);
final UsageStatsManagerInternal usageStats =
addLocalServiceMock(UsageStatsManagerInternal.class);
@@ -255,7 +281,8 @@
setCurrentTimeMillis(TEST_START);
- // intercept various broadcasts, and pretend that uids have packages
+ // Intercept various broadcasts, and pretend that uids have packages.
+ // Also return mock service instances for a few critical services.
mServiceContext = new BroadcastInterceptingContext(context) {
@Override
public PackageManager getPackageManager() {
@@ -266,6 +293,20 @@
public void startActivity(Intent intent) {
// ignored
}
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+ return mSubscriptionManager;
+ case Context.CARRIER_CONFIG_SERVICE:
+ return mCarrierConfigManager;
+ case Context.TELEPHONY_SERVICE:
+ return mTelephonyManager;
+ default:
+ return super.getSystemService(name);
+ }
+ }
};
setNetpolicyXml(context);
@@ -321,6 +362,10 @@
ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
verify(mNetworkManager).registerObserver(networkObserver.capture());
mNetworkObserver = networkObserver.getValue();
+
+ NetworkPolicy defaultPolicy = mService.buildDefaultMobilePolicy(0, "");
+ mDefaultWarningBytes = defaultPolicy.warningBytes;
+ mDefaultLimitBytes = defaultPolicy.limitBytes;
}
@After
@@ -1132,6 +1177,269 @@
}
}
+ private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay,
+ boolean expectValid) {
+ config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay);
+ int actualCycleDay = mService.getCycleDayFromCarrierConfig(config,
+ INVALID_CARRIER_CONFIG_VALUE);
+ if (expectValid) {
+ assertEquals(carrierCycleDay, actualCycleDay);
+ } else {
+ // INVALID_CARRIER_CONFIG_VALUE is returned for invalid values
+ assertEquals(INVALID_CARRIER_CONFIG_VALUE, actualCycleDay);
+ }
+ }
+
+ @Test
+ public void testGetCycleDayFromCarrierConfig() {
+ PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+ final Calendar cal = Calendar.getInstance();
+ int actualCycleDay;
+
+ config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ actualCycleDay = mService.getCycleDayFromCarrierConfig(config, DEFAULT_CYCLE_DAY);
+ assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay);
+
+ // null config returns a default value
+ actualCycleDay = mService.getCycleDayFromCarrierConfig(null, DEFAULT_CYCLE_DAY);
+ assertEquals(DEFAULT_CYCLE_DAY, actualCycleDay);
+
+ // Sane, non-default values
+ assertCycleDayAsExpected(config, 1, true);
+ assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH), true);
+ assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH), true);
+
+ // Invalid values
+ assertCycleDayAsExpected(config, 0, false);
+ assertCycleDayAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, false);
+ assertCycleDayAsExpected(config, cal.getMaximum(Calendar.DAY_OF_MONTH) + 1, false);
+ assertCycleDayAsExpected(config, cal.getMinimum(Calendar.DAY_OF_MONTH) - 5, false);
+ }
+
+ private void assertWarningBytesAsExpected(PersistableBundle config, long carrierWarningBytes,
+ long expected) {
+ config.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, carrierWarningBytes);
+ long actualWarning = mService.getWarningBytesFromCarrierConfig(config,
+ INVALID_CARRIER_CONFIG_VALUE);
+ assertEquals(expected, actualWarning);
+ }
+
+ @Test
+ public void testGetWarningBytesFromCarrierConfig() {
+ PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+ long actualWarningBytes;
+
+ assertWarningBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT,
+ mDefaultWarningBytes);
+ assertWarningBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, WARNING_DISABLED);
+ assertWarningBytesAsExpected(config, 0, 0);
+ // not a valid value
+ assertWarningBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE);
+
+ // null config returns a default value
+ actualWarningBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultWarningBytes);
+ assertEquals(mDefaultWarningBytes, actualWarningBytes);
+ }
+
+ private void assertLimitBytesAsExpected(PersistableBundle config, long carrierWarningBytes,
+ long expected) {
+ config.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, carrierWarningBytes);
+ long actualWarning = mService.getLimitBytesFromCarrierConfig(config,
+ INVALID_CARRIER_CONFIG_VALUE);
+ assertEquals(expected, actualWarning);
+ }
+
+ @Test
+ public void testGetLimitBytesFromCarrierConfig() {
+ PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+ long actualLimitBytes;
+
+ assertLimitBytesAsExpected(config, DATA_CYCLE_USE_PLATFORM_DEFAULT,
+ mDefaultLimitBytes);
+ assertLimitBytesAsExpected(config, DATA_CYCLE_THRESHOLD_DISABLED, LIMIT_DISABLED);
+ assertLimitBytesAsExpected(config, 0, 0);
+ // not a valid value
+ assertLimitBytesAsExpected(config, -1000, INVALID_CARRIER_CONFIG_VALUE);
+
+ // null config returns a default value
+ actualLimitBytes = mService.getWarningBytesFromCarrierConfig(null, mDefaultLimitBytes);
+ assertEquals(mDefaultLimitBytes, actualLimitBytes);
+ }
+
+ private PersistableBundle setupUpdateMobilePolicyCycleTests() throws RemoteException {
+ when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
+ when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
+ when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
+ setNetworkPolicies(buildDefaultFakeMobilePolicy());
+ return bundle;
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleWithNullConfig() throws RemoteException {
+ when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
+ when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
+ setNetworkPolicies(buildDefaultFakeMobilePolicy());
+ // smoke test to make sure no errors are raised
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+ assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+ true);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleWithInvalidConfig() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+ // Test with an invalid CarrierConfig, there should be no changes or crashes.
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, -100);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, -100);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, -100);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+ true);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleWithDefaultConfig() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+ // Test that we respect the platform values when told to
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+ true);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleWithUserOverrides() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+ // inferred = false implies that a user manually modified this policy.
+ NetworkPolicy policy = buildDefaultFakeMobilePolicy();
+ policy.inferred = false;
+ setNetworkPolicies(policy);
+
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_THRESHOLD_DISABLED);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ // The policy still shouldn't change, because we don't want to overwrite user settings.
+ assertNetworkPolicyEquals(DEFAULT_CYCLE_DAY, mDefaultWarningBytes, mDefaultLimitBytes,
+ false);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleUpdatesDataCycle() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, 9999);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, 9999);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ assertNetworkPolicyEquals(31, 9999, 9999, true);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleDisableThresholds() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_THRESHOLD_DISABLED);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_THRESHOLD_DISABLED);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true);
+ }
+
+ @Test
+ public void testUpdateMobilePolicyCycleRevertsToDefault() throws RemoteException {
+ PersistableBundle bundle = setupUpdateMobilePolicyCycleTests();
+
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT, 31);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_THRESHOLD_DISABLED);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_THRESHOLD_DISABLED);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+ assertNetworkPolicyEquals(31, WARNING_DISABLED, LIMIT_DISABLED, true);
+
+ // If the user switches carriers to one that doesn't use a CarrierConfig, we should revert
+ // to the default data limit and warning. The cycle date doesn't need to revert as it's
+ // arbitrary anyways.
+ bundle.putInt(CarrierConfigManager.KEY_MONTHLY_DATA_CYCLE_DAY_INT,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_WARNING_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ bundle.putLong(CarrierConfigManager.KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG,
+ DATA_CYCLE_USE_PLATFORM_DEFAULT);
+ mServiceContext.sendBroadcast(
+ new Intent(ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, FAKE_SUB_ID)
+ );
+
+ assertNetworkPolicyEquals(31, mDefaultWarningBytes, mDefaultLimitBytes,
+ true);
+ }
+
+ private NetworkPolicy buildDefaultFakeMobilePolicy() {
+ NetworkPolicy p = mService.buildDefaultMobilePolicy(FAKE_SUB_ID, FAKE_SUBSCRIBER_ID);
+ // set a deterministic cycle date
+ p.cycleDay = DEFAULT_CYCLE_DAY;
+ return p;
+ }
+
+ private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes,
+ long limitBytes, boolean inferred){
+ final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID);
+ return new NetworkPolicy(template, cycleDay, "America/Los_Angeles", warningBytes,
+ limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
+ }
+
+ private void assertNetworkPolicyEquals(int expectedCycleDay, long expectedWarningBytes,
+ long expectedLimitBytes, boolean expectedInferred) {
+ NetworkPolicy[] policies = mService.getNetworkPolicies(
+ mServiceContext.getOpPackageName());
+ assertEquals("Unexpected number of network policies", 1, policies.length);
+ NetworkPolicy actualPolicy = policies[0];
+ NetworkPolicy expectedPolicy = buildFakeMobilePolicy(expectedCycleDay, expectedWarningBytes,
+ expectedLimitBytes, expectedInferred);
+ assertEquals(expectedPolicy, actualPolicy);
+ }
+
private static long parseTime(String time) {
final Time result = new Time();
result.parse3339(time);
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..1729cee 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,68 @@
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());
+ }
+
+ /**
+ * This tests setting the maximum ui width on a display.
+ */
+ @Test
+ public void testMaxUiWidth() throws Exception {
+ final int baseWidth = 1440;
+ final int baseHeight = 2560;
+ final int baseDensity = 300;
+
+ sDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+
+ final int maxWidth = 300;
+ final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
+ final int resultingDensity = (maxWidth * baseDensity) / baseWidth;
+
+ sDisplayContent.setMaxUiWidth(maxWidth);
+ verifySizes(sDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+ // Assert setting values again does not change;
+ sDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
+ verifySizes(sDisplayContent, maxWidth, resultingHeight, resultingDensity);
+
+ final int smallerWidth = 200;
+ final int smallerHeight = 400;
+ final int smallerDensity = 100;
+
+ // Specify smaller dimension, verify that it is honored
+ sDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity);
+ verifySizes(sDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+
+ // Verify that setting the max width to a greater value than the base width has no effect
+ sDisplayContent.setMaxUiWidth(maxWidth);
+ verifySizes(sDisplayContent, smallerWidth, smallerHeight, smallerDensity);
+ }
+
+ private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
+ int expectedBaseHeight, int expectedBaseDensity) {
+ assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth);
+ assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight);
+ assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity);
+ }
+
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);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 1b28db7..e55e073 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -20,6 +20,7 @@
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.os.Build;
+import android.os.SystemProperties;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.TimeUtils;
@@ -56,6 +57,9 @@
private static final boolean DEBUG = UsageStatsService.DEBUG;
private static final String BAK_SUFFIX = ".bak";
private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX;
+ private static final String RETENTION_LEN_KEY = "ro.usagestats.chooser.retention";
+ private static final int SELECTION_LOG_RETENTION_LEN =
+ SystemProperties.getInt(RETENTION_LEN_KEY, 14);
private final Object mLock = new Object();
private final File[] mIntervalDirs;
@@ -504,7 +508,7 @@
mCal.getTimeInMillis());
mCal.setTimeInMillis(currentTimeMillis);
- mCal.addDays(-14);
+ mCal.addDays(-SELECTION_LOG_RETENTION_LEN);
for (int i = 0; i < mIntervalDirs.length; ++i) {
pruneChooserCountsOlderThan(mIntervalDirs[i], mCal.getTimeInMillis());
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index f1cf441..0878e8b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -554,7 +554,7 @@
boolean usbDataUnlocked) {
if (DEBUG) {
Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
- + "forceRestart=" + forceRestart);
+ + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
}
if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -641,8 +641,11 @@
// Set the new USB configuration.
setUsbConfig(oemFunctions);
- // Start up dependent services.
- updateUsbStateBroadcastIfNeeded(true);
+ if (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
+ || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP)) {
+ // Start up dependent services.
+ updateUsbStateBroadcastIfNeeded(true);
+ }
if (!waitForState(oemFunctions)) {
Slog.e(TAG, "Failed to switch USB config to " + functions);
@@ -878,7 +881,12 @@
setEnabledFunctions(functions, false, msg.arg1 == 1);
break;
case MSG_UPDATE_USER_RESTRICTIONS:
- setEnabledFunctions(mCurrentFunctions, false, mUsbDataUnlocked);
+ // Restart the USB stack if USB transfer is enabled but no longer allowed.
+ final boolean forceRestart = mUsbDataUnlocked
+ && isUsbDataTransferActive()
+ && !isUsbTransferAllowed();
+ setEnabledFunctions(
+ mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart);
break;
case MSG_SYSTEM_READY:
updateUsbNotification();
@@ -902,12 +910,10 @@
case MSG_USER_SWITCHED: {
if (mCurrentUser != msg.arg1) {
// Restart the USB stack and re-apply user restrictions for MTP or PTP.
- final boolean active = UsbManager.containsFunction(mCurrentFunctions,
- UsbManager.USB_FUNCTION_MTP)
- || UsbManager.containsFunction(mCurrentFunctions,
- UsbManager.USB_FUNCTION_PTP);
- if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) {
- Slog.v(TAG, "Current user switched to " + mCurrentUser
+ if (mUsbDataUnlocked
+ && isUsbDataTransferActive()
+ && mCurrentUser != UserHandle.USER_NULL) {
+ Slog.v(TAG, "Current user switched to " + msg.arg1
+ "; resetting USB host stack for MTP or PTP");
// avoid leaking sensitive data from previous user
setEnabledFunctions(mCurrentFunctions, true, false);
@@ -928,6 +934,11 @@
}
}
+ private boolean isUsbDataTransferActive() {
+ return UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
+ || UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
+ }
+
public UsbAccessory getCurrentAccessory() {
return mCurrentAccessory;
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c790902..92233b1 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -125,8 +125,9 @@
public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
/**
- * Extra key used to indicate the time (in millis) when the last outgoing emergency call was
- * made. This is used to identify potential emergency callbacks.
+ * Extra key used to indicate the time (in milliseconds since midnight, January 1, 1970 UTC)
+ * when the last outgoing emergency call was made. This is used to identify potential emergency
+ * callbacks.
*/
public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS =
"android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a226a0..2b4bce3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -771,6 +771,13 @@
public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
/**
+ * Determines whether the Enhanced 4G LTE toggle will be shown in the settings. When this
+ * option is {@code true}, the toggle will be hidden regardless of whether the device and
+ * carrier supports 4G LTE or not.
+ */
+ public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
+
+ /**
* Determine whether IMS apn can be shown.
*/
public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
@@ -1522,6 +1529,7 @@
sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
+ sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index 74b20f4..0e8bad7 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -32,7 +32,7 @@
}
public ImsException(String message, int code) {
- super(message + ", code = " + code);
+ super(message + "(" + code + ")");
mCode = code;
}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index bcaac6e..73ee25a 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -261,7 +261,7 @@
* by the system.
*/
public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
- = "android.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
+ = "com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
/**
* <p>Broadcast Action: Indicates that the action is forbidden by network.
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 506f406..960a2d9 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -1118,4 +1118,12 @@
public int getInstallReason(String packageName, UserHandle user) {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 04443a5..f22ad1d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,7 +23,12 @@
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkCapabilities.*;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -33,6 +38,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -76,11 +82,16 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult;
import com.android.server.net.NetworkPinner;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -133,8 +144,19 @@
private class MockContext extends BroadcastInterceptingContext {
private final MockContentResolver mContentResolver;
+ @Spy private Resources mResources;
+
MockContext(Context base) {
super(base);
+
+ mResources = spy(base.getResources());
+ when(mResources.getStringArray(com.android.internal.R.array.networkAttributes)).
+ thenReturn(new String[] {
+ "wifi,1,1,1,-1,true",
+ "mobile,0,0,0,-1,true",
+ "mobile_mms,2,0,2,60000,true",
+ });
+
mContentResolver = new MockContentResolver();
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
}
@@ -150,6 +172,11 @@
public ContentResolver getContentResolver() {
return mContentResolver;
}
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
}
/**
@@ -620,6 +647,7 @@
private class WrappedConnectivityService extends ConnectivityService {
public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+ private MockableSystemProperties mSystemProperties;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -629,9 +657,13 @@
}
@Override
- protected int getDefaultTcpRwnd() {
- // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
- return 0;
+ protected MockableSystemProperties getSystemProperties() {
+ // Minimal approach to overriding system properties: let most calls fall through to real
+ // device values, and only override ones values that are important to this test.
+ mSystemProperties = spy(new MockableSystemProperties());
+ when(mSystemProperties.getInt("net.tcp.default_init_rwnd", 0)).thenReturn(0);
+ when(mSystemProperties.getBoolean("ro.radio.noril", false)).thenReturn(false);
+ return mSystemProperties;
}
@Override
@@ -801,6 +833,14 @@
return cv;
}
+ public void testNetworkTypes() {
+ // Ensure that our mocks for the networkAttributes config variable work as expected. If they
+ // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
+ // will fail. Failing here is much easier to debug.
+ assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
+ assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
+ }
+
@SmallTest
public void testLingering() throws Exception {
verifyNoNetwork();
diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java
index 53841d5..b09bcde 100644
--- a/tests/testables/src/android/testing/BaseFragmentTest.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -133,14 +133,7 @@
public void testRecreate() {
mFragments.dispatchResume();
processAllMessages();
- mFragments.dispatchPause();
- Parcelable p = mFragments.saveAllState();
- mFragments.dispatchDestroy();
-
- mFragments = FragmentController.createController(new HostCallbacks());
- mFragments.attachHost(null);
- mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
- mFragments.dispatchResume();
+ recreateFragment();
processAllMessages();
}
@@ -154,6 +147,18 @@
processAllMessages();
}
+ protected void recreateFragment() {
+ mFragments.dispatchPause();
+ Parcelable p = mFragments.saveAllState();
+ mFragments.dispatchDestroy();
+
+ mFragments = FragmentController.createController(new HostCallbacks());
+ mFragments.attachHost(null);
+ mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
+ mFragments.dispatchResume();
+ mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID);
+ }
+
protected void attachFragmentToWindow() {
ViewUtils.attachView(mView);
TestableLooper.get(this).processMessages(1);
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 3330b1a..2bf5206 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1403,7 +1403,8 @@
String8 src = it.getFile()->getPrintableSource();
err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
it.getFile(), &table, xmlFlags);
- if (err == NO_ERROR) {
+ // Only verify IDs if there was no error and the file is non-empty.
+ if (err == NO_ERROR && it.getFile()->hasData()) {
ResXMLTree block;
block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
checkForIds(src, block);
@@ -1550,7 +1551,7 @@
String8 src = it.getFile()->getPrintableSource();
err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
it.getFile(), &table, xmlFlags);
- if (err == NO_ERROR) {
+ if (err == NO_ERROR && it.getFile()->hasData()) {
ResXMLTree block;
block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
checkForIds(src, block);
@@ -1598,7 +1599,7 @@
err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
workItem.file, &table, xmlCompilationFlags);
- if (err == NO_ERROR) {
+ if (err == NO_ERROR && workItem.file->hasData()) {
assets->addResource(workItem.resPath.getPathLeaf(),
workItem.resPath,
workItem.file,
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 391aa47..221f3c2 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -78,6 +78,17 @@
ResourceTable* table,
int options)
{
+ if (table->versionForCompat(bundle, resourceName, target, root)) {
+ // The file was versioned, so stop processing here.
+ // The resource entry has already been removed and the new one added.
+ // Remove the assets entry.
+ sp<AaptDir> resDir = assets->getDirs().valueFor(String8("res"));
+ sp<AaptDir> dir = resDir->getDirs().valueFor(target->getGroupEntry().toDirName(
+ target->getResourceType()));
+ dir->removeFile(target->getPath().getPathLeaf());
+ return NO_ERROR;
+ }
+
if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
root->removeWhitespace(true, NULL);
} else if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
@@ -4758,6 +4769,77 @@
return false;
}
+bool ResourceTable::versionForCompat(const Bundle* bundle, const String16& resourceName,
+ const sp<AaptFile>& target, const sp<XMLNode>& root) {
+ XMLNode* node = root.get();
+ while (node->getType() != XMLNode::TYPE_ELEMENT) {
+ // We're assuming the root element is what we're looking for, which can only be under a
+ // bunch of namespace declarations.
+ if (node->getChildren().size() != 1) {
+ // Not sure what to do, bail.
+ return false;
+ }
+ node = node->getChildren().itemAt(0).get();
+ }
+
+ if (node->getElementNamespace().size() != 0) {
+ // Not something we care about.
+ return false;
+ }
+
+ int versionedSdk = 0;
+ if (node->getElementName() == String16("adaptive-icon")) {
+ versionedSdk = SDK_O;
+ }
+
+ const int minSdkVersion = getMinSdkVersion(bundle);
+ const ConfigDescription config(target->getGroupEntry().toParams());
+ if (versionedSdk <= minSdkVersion || versionedSdk <= config.sdkVersion) {
+ return false;
+ }
+
+ sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
+ String16(target->getResourceType()), resourceName);
+ if (!shouldGenerateVersionedResource(cl, config, versionedSdk)) {
+ return false;
+ }
+
+ // Remove the original entry.
+ cl->removeEntry(config);
+
+ // We need to wholesale version this file.
+ ConfigDescription newConfig(config);
+ newConfig.sdkVersion = versionedSdk;
+ sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
+ AaptGroupEntry(newConfig), target->getResourceType());
+ String8 resPath = String8::format("res/%s/%s.xml",
+ newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
+ String8(resourceName).string());
+ resPath.convertToResPath();
+
+ // Add a resource table entry.
+ addEntry(SourcePos(),
+ String16(mAssets->getPackage()),
+ String16(target->getResourceType()),
+ resourceName,
+ String16(resPath),
+ NULL,
+ &newConfig);
+
+ // Schedule this to be compiled.
+ CompileResourceWorkItem item;
+ item.resourceName = resourceName;
+ item.resPath = resPath;
+ item.file = newFile;
+ item.xmlRoot = root->clone();
+ item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
+ // to do it again.
+ mWorkQueue.push(item);
+
+ // Now mark the old entry as deleted.
+ return true;
+}
+
status_t ResourceTable::modifyForCompat(const Bundle* bundle,
const String16& resourceName,
const sp<AaptFile>& target,
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index cf1e992..aff22d4 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -203,6 +203,9 @@
size_t numLocalResources() const;
bool hasResources() const;
+ bool versionForCompat(const Bundle* bundle, const String16& resourceName,
+ const sp<AaptFile>& file, const sp<XMLNode>& root);
+
status_t modifyForCompat(const Bundle* bundle);
status_t modifyForCompat(const Bundle* bundle,
const String16& resourceName,
@@ -431,6 +434,10 @@
mEntries.add(config, entry);
}
+ void removeEntry(const ResTable_config& config) {
+ mEntries.removeItem(config);
+ }
+
const DefaultKeyedVector<ConfigDescription, sp<Entry> >& getEntries() const { return mEntries; }
private:
const String16 mName;
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index e3bc34b..c20ee12 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -33,7 +33,6 @@
import com.android.layoutlib.bridge.util.NinePatchInputStream;
import com.android.ninepatch.NinePatch;
import com.android.resources.ResourceType;
-import com.android.resources.ResourceUrl;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.util.Pair;
@@ -60,8 +59,6 @@
import java.io.InputStream;
import java.util.Iterator;
-import static com.android.SdkConstants.ANDROID_NS_NAME;
-
@SuppressWarnings("deprecation")
public class Resources_Delegate {
@@ -140,8 +137,8 @@
if (value == null) {
// Unable to resolve the attribute, just leave the unresolved value
- value = new ResourceValue(ResourceUrl.create(resourceInfo.getFirst(), attributeName,
- platformResFlag_out[0]), attributeName);
+ value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
+ platformResFlag_out[0]);
}
return Pair.of(attributeName, value);
}
@@ -681,7 +678,7 @@
String packageName;
if (resourceInfo != null) {
if (platformOut[0]) {
- packageName = ANDROID_NS_NAME;
+ packageName = SdkConstants.ANDROID_NS_NAME;
} else {
packageName = resources.mContext.getPackageName();
packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
@@ -699,7 +696,7 @@
Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
if (resourceInfo != null) {
if (platformOut[0]) {
- return ANDROID_NS_NAME;
+ return SdkConstants.ANDROID_NS_NAME;
}
String packageName = resources.mContext.getPackageName();
return packageName == null ? SdkConstants.APP_PREFIX : packageName;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index e118889..80e3bad 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -233,7 +233,8 @@
Map<String, ByteBuffer> bufferForPath) {
FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
for (FontConfig.Font font : family.getFonts()) {
- FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(),
+ String fullPathName = "/system/fonts/" + font.getFontName();
+ FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName,
font.getWeight(), font.isItalic());
}
fontFamily.freeze();
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index 43f4ebc..e4b2020 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -127,6 +127,9 @@
private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+ if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
+ return;
+ }
// The polygon of shadow (same as the original item)
float[] shadowPoly = new float[coordinates.length * 3];
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 432fdda..4573f7a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -31,7 +31,6 @@
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.Stack;
import com.android.resources.ResourceType;
-import com.android.resources.ResourceUrl;
import com.android.util.Pair;
import com.android.util.PropertiesMap;
import com.android.util.PropertiesMap.Property;
@@ -87,6 +86,7 @@
import android.view.BridgeInflater;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,7 +107,6 @@
import java.util.Map;
import static android.os._Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static com.android.SdkConstants.ANDROID_NS_NAME;
import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
/**
@@ -122,23 +121,20 @@
static {
FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
- ResourceUrl.create(ANDROID_NS_NAME, ResourceType.BOOL, "animateFirstView"),
- "false"));
- FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges", new ResourceValue(
- ResourceUrl.create(ANDROID_NS_NAME, ResourceType.BOOL, "animateLayoutChanges"),
- "false"));
+ ResourceType.BOOL, "animateFirstView", "false", false));
+ FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
+ new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout", new ResourceValue(
- ResourceUrl.create(ANDROID_NS_NAME, ResourceType.LAYOUT,
- "textEditSuggestionItemLayout"), "text_edit_suggestion_item"));
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout", new ResourceValue(
- ResourceUrl.create(ANDROID_NS_NAME, ResourceType.LAYOUT,
- "textEditSuggestionContainerLayout"), "text_edit_suggestion_container"));
- FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle", new ResourceValue(
- ResourceUrl.create(ANDROID_NS_NAME, ResourceType.STYLE,
- "textEditSuggestionHighlightStyle"),
- "TextAppearance.Holo.SuggestionHighlight"));
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
+ new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
+ "text_edit_suggestion_item", true));
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
+ new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
+ "text_edit_suggestion_container", true));
+ FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
+ new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
+ "TextAppearance.Holo.SuggestionHighlight", true));
}
@@ -648,6 +644,10 @@
return null;
}
+ if (AUDIO_SERVICE.equals(service)) {
+ return null;
+ }
+
throw new UnsupportedOperationException("Unsupported Service: " + service);
}
@@ -967,9 +967,7 @@
// there is a value in the XML, but we need to resolve it in case it's
// referencing another resource or a theme value.
ta.bridgeSetValue(index, attrName, frameworkAttr,
- mRenderResources.resolveResValue(new ResourceValue(
- ResourceUrl.create(ResourceType.STRING, attrName,
- isPlatformFile), value)));
+ mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
}
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 906ebb1..53c3f90 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -916,4 +916,9 @@
public boolean canRequestPackageInstalls() {
return false;
}
+
+ @Override
+ public ComponentName getInstantAppResolverSettingsComponent() {
+ return null;
+ }
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index 957831d..736b287 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 00dddee..8739b7f 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -311,7 +311,6 @@
sFrameworkRepo = null;
sProjectResources = null;
sLogger = null;
- sBridge.dispose();
sBridge = null;
TestUtils.gc();
@@ -329,7 +328,6 @@
RenderSession session = sBridge.createSession(params);
try {
-
if (frameTimeNanos != -1) {
session.setElapsedFrameTimeNanos(frameTimeNanos);
}
@@ -338,11 +336,13 @@
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
}
- // Render the session with a timeout of 50s.
- Result renderResult = session.render(50000);
- if (!renderResult.isSuccess()) {
- getLogger().error(session.getResult().getException(),
- session.getResult().getErrorMessage());
+ else {
+ // Render the session with a timeout of 50s.
+ Result renderResult = session.render(50000);
+ if (!renderResult.isSuccess()) {
+ getLogger().error(session.getResult().getException(),
+ session.getResult().getErrorMessage());
+ }
}
return RenderResult.getFromSession(session);
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 10ffd8a..1852feb 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -81,8 +81,6 @@
boolean disableNetwork(int netId);
- boolean pingSupplicant();
-
void startScan(in ScanSettings requested, in WorkSource ws);
List<ScanResult> getScanResults(String callingPackage);
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index eceb365..4387b0c 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -18,11 +18,10 @@
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -140,8 +139,8 @@
// (1) connect + success
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
- clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(binder.capture(), any(),
+ clientProxyCallback.capture(), isNull(), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -150,8 +149,7 @@
// (2) publish - should succeed
PublishConfig publishConfig = new PublishConfig.Builder().build();
session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
- any(IWifiAwareDiscoverySessionCallback.class));
+ inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig), any());
// (3) disconnect
session.destroy();
@@ -163,8 +161,8 @@
// (5) connect
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
- any(IWifiAwareEventCallback.class), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(binder.capture(), any(), any(), isNull(),
+ eq(false));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
}
@@ -185,16 +183,16 @@
// (1) connect + failure
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ isNull(), eq(false));
clientProxyCallback.getValue().onConnectFail(reason);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttachFailed();
// (2) connect + success
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ isNull(), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -203,8 +201,7 @@
// (4) subscribe: should succeed
SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
- any(IWifiAwareDiscoverySessionCallback.class));
+ inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig), any());
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
}
@@ -223,19 +220,19 @@
// (1) connect + success
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ isNull(), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
- inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+ inOrder.verify(mockCallback).onAttached(any());
// (2) connect + success
mDut.attach(mockCallback, mMockLooperHandler);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ isNull(), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId + 1);
mMockLooper.dispatchAll();
- inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+ inOrder.verify(mockCallback).onAttached(any());
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
}
@@ -278,8 +275,8 @@
// (0) connect + success
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -370,8 +367,8 @@
// (1) connect successfully
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -426,8 +423,8 @@
// (0) connect + success
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -507,8 +504,8 @@
// (1) connect successfully
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -899,8 +896,7 @@
final RttManager.RttResult rttResults = new RttManager.RttResult();
rttResults.distance = 10;
- when(mockAwareService.startRanging(anyInt(), anyInt(),
- any(RttManager.ParcelableRttParams.class))).thenReturn(rangingId);
+ when(mockAwareService.startRanging(anyInt(), anyInt(), any())).thenReturn(rangingId);
InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
@@ -919,8 +915,8 @@
// (1) connect successfully
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -994,8 +990,8 @@
// (1) connect successfully
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
@@ -1085,8 +1081,8 @@
// (1) connect successfully
mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
- inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
- clientProxyCallback.capture(), eq(configRequest), eq(false));
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
clientProxyCallback.getValue().onConnectSuccess(clientId);
mMockLooper.dispatchAll();
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());