Merge "Log USB contaminant status"
diff --git a/Android.bp b/Android.bp
index 8a2089f..a5cc89c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -49,6 +49,9 @@
"rs/java/**/*.java",
":framework-javastream-protos",
+ // TODO: Resolve circular library dependency and remove media1-srcs and mediasession2-srcs
+ ":media1-srcs",
+ ":mediasession2-srcs",
"core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
"core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl",
@@ -471,14 +474,11 @@
"media/java/android/media/IAudioRoutesObserver.aidl",
"media/java/android/media/IAudioService.aidl",
"media/java/android/media/IAudioServerStateDispatcher.aidl",
- "media/java/android/media/IMediaController2.aidl",
"media/java/android/media/IMediaHTTPConnection.aidl",
"media/java/android/media/IMediaHTTPService.aidl",
"media/java/android/media/IMediaResourceMonitor.aidl",
"media/java/android/media/IMediaRouterClient.aidl",
"media/java/android/media/IMediaRouterService.aidl",
- "media/java/android/media/IMediaSession2.aidl",
- "media/java/android/media/IMediaSession2Service.aidl",
"media/java/android/media/IMediaScannerListener.aidl",
"media/java/android/media/IMediaScannerService.aidl",
"media/java/android/media/IPlaybackConfigDispatcher.aidl",
@@ -504,11 +504,7 @@
"media/java/android/media/session/ICallback.aidl",
"media/java/android/media/session/IOnMediaKeyListener.aidl",
"media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl",
- "media/java/android/media/session/ISession.aidl",
"media/java/android/media/session/ISession2TokensListener.aidl",
- "media/java/android/media/session/ISessionCallback.aidl",
- "media/java/android/media/session/ISessionController.aidl",
- "media/java/android/media/session/ISessionControllerCallback.aidl",
"media/java/android/media/session/ISessionManager.aidl",
"media/java/android/media/soundtrigger/ISoundTriggerDetectionService.aidl",
"media/java/android/media/soundtrigger/ISoundTriggerDetectionServiceClient.aidl",
@@ -523,8 +519,6 @@
"media/java/android/media/tv/ITvInputSessionCallback.aidl",
"media/java/android/media/tv/ITvRemoteProvider.aidl",
"media/java/android/media/tv/ITvRemoteServiceInput.aidl",
- "media/java/android/service/media/IMediaBrowserService.aidl",
- "media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
"telecomm/java/com/android/internal/telecom/ICallRedirectionAdapter.aidl",
"telecomm/java/com/android/internal/telecom/ICallRedirectionService.aidl",
"telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl",
@@ -637,6 +631,7 @@
"wifi/java/android/net/wifi/ISoftApCallback.aidl",
"wifi/java/android/net/wifi/ITrafficStateCallback.aidl",
"wifi/java/android/net/wifi/IWifiManager.aidl",
+ "wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl",
@@ -691,6 +686,7 @@
"location/java",
"lowpan/java",
"media/java",
+ "media/apex/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
"media/mca/filterpacks/java",
@@ -723,8 +719,6 @@
exclude_srcs: [
// See comment on framework-atb-backward-compatibility module below
"core/java/android/content/pm/AndroidTestBaseUpdater.java",
- // See comment on framework-oahl-backward-compatibility module below
- "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
],
no_framework_libs: true,
@@ -1268,7 +1262,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
- ":media2-srcs",
+ ":media-srcs-without-aidls",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
],
@@ -1330,7 +1324,7 @@
":non_openjdk_javadoc_files",
":android_icu4j_src_files_for_docs",
":conscrypt_public_api_files",
- ":media2-srcs",
+ ":media-srcs-without-aidls",
],
srcs_lib: "framework",
srcs_lib_whitelist_dirs: frameworks_base_subdirs,
@@ -1776,6 +1770,7 @@
name: "framework-media-annotation-srcs",
srcs: [
"core/java/android/annotation/CallbackExecutor.java",
+ "core/java/android/annotation/CallSuper.java",
"core/java/android/annotation/DrawableRes.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/LongDef.java",
@@ -1784,6 +1779,7 @@
"core/java/android/annotation/RequiresPermission.java",
"core/java/android/annotation/SdkConstant.java",
"core/java/android/annotation/StringDef.java",
+ "core/java/android/annotation/SystemApi.java",
"core/java/android/annotation/TestApi.java",
"core/java/android/annotation/UnsupportedAppUsage.java",
"core/java/com/android/internal/annotations/GuardedBy.java",
diff --git a/api/current.txt b/api/current.txt
index b29e19b..abb0e21 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5793,6 +5793,7 @@
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method @Nullable public String getNotificationDelegate();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationAssistantAccessGranted(android.content.ComponentName);
method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
@@ -6578,7 +6579,6 @@
}
public class DevicePolicyManager {
- method public void addCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public int addOverrideApn(@NonNull android.content.ComponentName, @NonNull android.telephony.data.ApnSetting);
@@ -6608,7 +6608,7 @@
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
- method @NonNull public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+ method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
@@ -6698,7 +6698,6 @@
method public int logoutUser(@NonNull android.content.ComponentName);
method public void reboot(@NonNull android.content.ComponentName);
method public void removeActiveAdmin(@NonNull android.content.ComponentName);
- method public boolean removeCrossProfileCalendarPackage(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
method public boolean removeKeyPair(@Nullable android.content.ComponentName, @NonNull String);
method public boolean removeOverrideApn(@NonNull android.content.ComponentName, int);
@@ -6720,6 +6719,7 @@
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
+ method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
@@ -8610,6 +8610,13 @@
method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
}
+ public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
method public boolean connect(android.bluetooth.BluetoothDevice);
method public boolean disconnect(android.bluetooth.BluetoothDevice);
@@ -8705,6 +8712,7 @@
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
field @Deprecated public static final int HEALTH = 3; // 0x3
+ field public static final int HEARING_AID = 21; // 0x15
field public static final int HID_DEVICE = 19; // 0x13
field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
@@ -11210,6 +11218,7 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle);
+ method @Nullable public android.content.pm.LauncherApps.AppUsageLimit getAppUsageLimit(String, android.os.UserHandle);
method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent);
method public java.util.List<android.os.UserHandle> getProfiles();
@@ -11237,6 +11246,14 @@
field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
}
+ public static final class LauncherApps.AppUsageLimit implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getTotalUsageLimit();
+ method public long getUsageRemaining();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
+ }
+
public abstract static class LauncherApps.Callback {
ctor public LauncherApps.Callback();
method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -11433,6 +11450,7 @@
method public void setAppIcon(@Nullable android.graphics.Bitmap);
method public void setAppLabel(@Nullable CharSequence);
method public void setAppPackageName(@Nullable String);
+ method public void setInstallAsApex();
method public void setInstallLocation(int);
method public void setInstallReason(int);
method public void setMultiPackage();
@@ -11675,6 +11693,7 @@
field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+ field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
@@ -25410,6 +25429,7 @@
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public Object attachAuxEffect(int);
method public boolean cancelCommand(@NonNull Object);
+ method public void clearDrmEventCallback();
method public Object clearNextDataSources();
method public void clearPendingCommands();
method public void close();
@@ -28471,6 +28491,7 @@
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+ method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork();
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
@@ -28975,6 +28996,29 @@
ctor public SSLSessionCache(android.content.Context);
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ method public final void close();
+ method public final void start(@IntRange(from=0xa, to=0xe10) int);
+ method public final void stop();
+ field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+ field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2
+ field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+ field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+ }
+
+ public static class SocketKeepalive.Callback {
+ ctor public SocketKeepalive.Callback();
+ method public void onDataReceived();
+ method public void onError(int);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -29185,6 +29229,7 @@
method public android.os.ParcelFileDescriptor establish();
method public android.net.VpnService.Builder setBlocking(boolean);
method public android.net.VpnService.Builder setConfigureIntent(android.app.PendingIntent);
+ method public android.net.VpnService.Builder setHttpProxy(android.net.ProxyInfo);
method public android.net.VpnService.Builder setMtu(int);
method public android.net.VpnService.Builder setSession(String);
method public android.net.VpnService.Builder setUnderlyingNetworks(android.net.Network[]);
@@ -35251,11 +35296,16 @@
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createPrebaked(int);
method public static android.os.VibrationEffect createWaveform(long[], int);
method public static android.os.VibrationEffect createWaveform(long[], int[], int);
method public int describeContents();
field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ field public static final int EFFECT_CLICK = 0; // 0x0
+ field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
+ field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
+ field public static final int EFFECT_TICK = 2; // 0x2
}
public abstract class Vibrator {
@@ -39075,6 +39125,7 @@
field public static final String CONTENT_ID = "cid";
field public static final String CONTENT_LOCATION = "cl";
field public static final String CONTENT_TYPE = "ct";
+ field public static final android.net.Uri CONTENT_URI;
field public static final String CT_START = "ctt_s";
field public static final String CT_TYPE = "ctt_t";
field public static final String FILENAME = "fn";
@@ -41336,6 +41387,22 @@
package android.service.notification {
+ public final class Adjustment implements android.os.Parcelable {
+ ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
+ method public int describeContents();
+ method public CharSequence getExplanation();
+ method public String getKey();
+ method public String getPackage();
+ method public android.os.Bundle getSignals();
+ method public int getUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
+ field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+ field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+ field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+ }
+
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, String, int);
ctor public Condition(android.net.Uri, String, String, String, int, int, int);
@@ -41382,6 +41449,24 @@
field @Deprecated public static final String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
+ public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
+ ctor public NotificationAssistantService();
+ method public final void adjustNotification(android.service.notification.Adjustment);
+ method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
+ method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNotificationDirectReplied(@NonNull String);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+ method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
+ method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
+ method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
+ method public void onNotificationsSeen(java.util.List<java.lang.String>);
+ method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
+ field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
+ field public static final int SOURCE_FROM_APP = 0; // 0x0
+ field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
+ }
+
public abstract class NotificationListenerService extends android.app.Service {
ctor public NotificationListenerService();
method public final void cancelAllNotifications();
@@ -41462,6 +41547,8 @@
method public long getLastAudiblyAlertedMillis();
method public String getOverrideGroupKey();
method public int getRank();
+ method public java.util.List<android.app.Notification.Action> getSmartActions();
+ method public java.util.List<java.lang.CharSequence> getSmartReplies();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
@@ -41480,6 +41567,37 @@
field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
}
+ public final class NotificationStats implements android.os.Parcelable {
+ ctor public NotificationStats();
+ method public int describeContents();
+ method public int getDismissalSentiment();
+ method public int getDismissalSurface();
+ method public boolean hasDirectReplied();
+ method public boolean hasExpanded();
+ method public boolean hasInteracted();
+ method public boolean hasSeen();
+ method public boolean hasSnoozed();
+ method public boolean hasViewedSettings();
+ method public void setDirectReplied();
+ method public void setDismissalSentiment(int);
+ method public void setDismissalSurface(int);
+ method public void setExpanded();
+ method public void setSeen();
+ method public void setSnoozed();
+ method public void setViewedSettings();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+ field public static final int DISMISSAL_AOD = 2; // 0x2
+ field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+ field public static final int DISMISSAL_OTHER = 0; // 0x0
+ field public static final int DISMISSAL_PEEK = 1; // 0x1
+ field public static final int DISMISSAL_SHADE = 3; // 0x3
+ field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
+ field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
+ field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
+ field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
+ }
+
public class StatusBarNotification implements android.os.Parcelable {
ctor @Deprecated public StatusBarNotification(String, String, int, String, int, int, int, android.app.Notification, android.os.UserHandle, long);
ctor public StatusBarNotification(android.os.Parcel);
@@ -41674,6 +41792,7 @@
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
+ method public final void clearTranscription(boolean);
method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method public int getDisabledShowContext();
method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
@@ -41683,9 +41802,15 @@
method public void onReady();
method public void onShutdown();
method public void setDisabledShowContext(int);
+ method public final void setTranscription(@NonNull String);
+ method public final void setVoiceState(int);
method public void showSession(android.os.Bundle, int);
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
+ field public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1; // 0x1
+ field public static final int VOICE_STATE_FULFILLING = 3; // 0x3
+ field public static final int VOICE_STATE_LISTENING = 2; // 0x2
+ field public static final int VOICE_STATE_NONE = 0; // 0x0
}
public class VoiceInteractionSession implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback {
@@ -44672,6 +44797,7 @@
method public String getNumber();
method public int getSimSlotIndex();
method public int getSubscriptionId();
+ method public int getSubscriptionType();
method public boolean isEmbedded();
method public boolean isOpportunistic();
method public void writeToParcel(android.os.Parcel, int);
@@ -44722,6 +44848,8 @@
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+ field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+ field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
}
public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -52634,6 +52762,7 @@
public static final class ContentCaptureContext.Builder {
ctor public ContentCaptureContext.Builder();
method public android.view.contentcapture.ContentCaptureContext build();
+ method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setAction(@NonNull String);
method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.view.contentcapture.ContentCaptureContext.Builder setUri(@NonNull android.net.Uri);
}
@@ -52995,7 +53124,7 @@
method public void restartInput(android.view.View);
method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
method @Deprecated public void setAdditionalInputMethodSubtypes(String, android.view.inputmethod.InputMethodSubtype[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
method @Deprecated public void setInputMethod(android.os.IBinder, String);
method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
@@ -53092,6 +53221,16 @@
ctor public InspectionCompanion.UninitializedPropertyMapException();
}
+ public final class IntEnumMapping {
+ method @Nullable public String get(int);
+ }
+
+ public static final class IntEnumMapping.Builder {
+ ctor public IntEnumMapping.Builder();
+ method @NonNull public android.view.inspector.IntEnumMapping.Builder addValue(@NonNull String, int);
+ method @NonNull public android.view.inspector.IntEnumMapping build();
+ }
+
public final class IntFlagMapping {
method @NonNull public java.util.Set<java.lang.String> get(int);
}
@@ -53112,7 +53251,7 @@
method public int mapFloat(@NonNull String, @AttrRes int);
method public int mapGravity(@NonNull String, @AttrRes int);
method public int mapInt(@NonNull String, @AttrRes int);
- method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.util.SparseArray<java.lang.String>);
+ method public int mapIntEnum(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntEnumMapping);
method public int mapIntFlag(@NonNull String, @AttrRes int, @NonNull android.view.inspector.IntFlagMapping);
method public int mapLong(@NonNull String, @AttrRes int);
method public int mapObject(@NonNull String, @AttrRes int);
diff --git a/api/system-current.txt b/api/system-current.txt
index d43353f..315a941 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1113,6 +1113,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method public int getUsageSource();
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
method public void reportUsageStart(@NonNull android.app.Activity, @NonNull String);
@@ -1120,6 +1121,7 @@
method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
+ method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(String, long, android.os.UserHandle);
@@ -1148,7 +1150,7 @@
field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
}
- public abstract class BluetoothAdapter.MetadataListener {
+ public abstract static class BluetoothAdapter.MetadataListener {
ctor public BluetoothAdapter.MetadataListener();
method public void onMetadataChanged(android.bluetooth.BluetoothDevice, int, String);
}
@@ -4022,6 +4024,7 @@
}
public class ConnectivityManager {
+ method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method public boolean getAvoidBadWifi();
method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
@@ -4042,6 +4045,10 @@
method public void onTetheringStarted();
}
+ public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(java.net.InetAddress, int);
+ }
+
public final class IpSecManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
@@ -4074,6 +4081,7 @@
}
public class LinkAddress implements android.os.Parcelable {
+ ctor public LinkAddress(java.net.InetAddress, int, int, int);
ctor public LinkAddress(java.net.InetAddress, int);
ctor public LinkAddress(String);
method public boolean isGlobalPreferred();
@@ -4084,9 +4092,12 @@
public final class LinkProperties implements android.os.Parcelable {
ctor public LinkProperties();
+ ctor public LinkProperties(android.net.LinkProperties);
method public boolean addDnsServer(java.net.InetAddress);
method public boolean addRoute(android.net.RouteInfo);
method public void clear();
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
+ method public java.util.List<java.net.InetAddress> getPcscfServers();
method public String getTcpBufferSizes();
method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
method public boolean hasGlobalIPv6Address();
@@ -4104,6 +4115,8 @@
method public void setInterfaceName(String);
method public void setLinkAddresses(java.util.Collection<android.net.LinkAddress>);
method public void setMtu(int);
+ method public void setNat64Prefix(android.net.IpPrefix);
+ method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
method public void setPrivateDnsServerName(@Nullable String);
method public void setTcpBufferSizes(String);
method public void setUsePrivateDns(boolean);
@@ -4158,6 +4171,7 @@
}
public final class RouteInfo implements android.os.Parcelable {
+ ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
method public int getType();
field public static final int RTN_THROW = 9; // 0x9
field public static final int RTN_UNICAST = 1; // 0x1
@@ -4291,6 +4305,7 @@
}
public class IpConnectivityLog {
+ ctor public IpConnectivityLog();
method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -4339,6 +4354,20 @@
field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
}
+ public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class RaEvent.Builder {
+ ctor public RaEvent.Builder();
+ method public android.net.metrics.RaEvent build();
+ method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+ }
+
public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
method public static String getProbeName(int);
field public static final int DNS_FAILURE = 0; // 0x0
@@ -4610,6 +4639,7 @@
}
public class WifiManager {
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, android.net.wifi.WifiManager.ActionListener);
@@ -4625,6 +4655,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler);
+ method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -4682,6 +4713,10 @@
method public void select(@NonNull android.net.wifi.WifiConfiguration);
}
+ public static interface WifiManager.WifiUsabilityStatsListener {
+ method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry);
+ }
+
public class WifiNetworkConnectionStatistics implements android.os.Parcelable {
ctor public WifiNetworkConnectionStatistics(int, int);
ctor public WifiNetworkConnectionStatistics();
@@ -4811,6 +4846,31 @@
field @Deprecated public int unchangedSampleSize;
}
+ public final class WifiUsabilityStatsEntry implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.wifi.WifiUsabilityStatsEntry> CREATOR;
+ field public final int linkSpeedMbps;
+ field public final int rssi;
+ field public final long timeStampMs;
+ field public final long totalBackgroundScanTimeMs;
+ field public final long totalBeaconRx;
+ field public final long totalCcaBusyFreqTimeMs;
+ field public final long totalHotspot2ScanTimeMs;
+ field public final long totalNanScanTimeMs;
+ field public final long totalPnoScanTimeMs;
+ field public final long totalRadioOnFreqTimeMs;
+ field public final long totalRadioOnTimeMs;
+ field public final long totalRadioRxTimeMs;
+ field public final long totalRadioTxTimeMs;
+ field public final long totalRoamScanTimeMs;
+ field public final long totalRxSuccess;
+ field public final long totalScanTimeMs;
+ field public final long totalTxBad;
+ field public final long totalTxRetries;
+ field public final long totalTxSuccess;
+ }
+
}
package android.net.wifi.aware {
@@ -5572,6 +5632,12 @@
field public static final String OOB_WHITELIST = "oob_whitelist";
}
+ public static interface DeviceConfig.IntelligenceAttention {
+ field public static final String NAMESPACE = "intelligence_attention";
+ field public static final String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
+ field public static final String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+ }
+
public static interface DeviceConfig.OnPropertyChangedListener {
method public void onPropertyChanged(String, String, String);
}
@@ -6278,77 +6344,6 @@
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
@@ -7589,10 +7584,13 @@
public class SubscriptionManager {
method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
@@ -9112,6 +9110,7 @@
package android.view.contentcapture {
public final class ContentCaptureContext implements android.os.Parcelable {
+ method @Nullable public String getAction();
method @Nullable public android.content.ComponentName getActivityComponent();
method public int getDisplayId();
method @Nullable public android.os.Bundle getExtras();
diff --git a/api/test-current.txt b/api/test-current.txt
index c746882..223ddb9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -802,11 +802,16 @@
field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
}
+ public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(java.net.InetAddress, int);
+ }
+
public final class IpSecManager {
field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
}
public class LinkAddress implements android.os.Parcelable {
+ ctor public LinkAddress(java.net.InetAddress, int, int, int);
method public boolean isGlobalPreferred();
method public boolean isIPv4();
method public boolean isIPv6();
@@ -814,7 +819,10 @@
}
public final class LinkProperties implements android.os.Parcelable {
+ ctor public LinkProperties(android.net.LinkProperties);
method public boolean addDnsServer(java.net.InetAddress);
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
+ method public java.util.List<java.net.InetAddress> getPcscfServers();
method public String getTcpBufferSizes();
method public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
method public boolean hasGlobalIPv6Address();
@@ -826,6 +834,8 @@
method public boolean isReachable(java.net.InetAddress);
method public boolean removeDnsServer(java.net.InetAddress);
method public boolean removeRoute(android.net.RouteInfo);
+ method public void setNat64Prefix(android.net.IpPrefix);
+ method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
method public void setPrivateDnsServerName(@Nullable String);
method public void setTcpBufferSizes(String);
method public void setUsePrivateDns(boolean);
@@ -843,6 +853,7 @@
}
public final class RouteInfo implements android.os.Parcelable {
+ ctor public RouteInfo(android.net.IpPrefix, java.net.InetAddress, String, int);
method public int getType();
field public static final int RTN_THROW = 9; // 0x9
field public static final int RTN_UNICAST = 1; // 0x1
@@ -929,6 +940,7 @@
}
public class IpConnectivityLog {
+ ctor public IpConnectivityLog();
method public boolean log(long, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(String, android.net.metrics.IpConnectivityLog.Event);
method public boolean log(android.net.Network, int[], android.net.metrics.IpConnectivityLog.Event);
@@ -977,6 +989,20 @@
field public static final int NETWORK_VALIDATION_FAILED = 3; // 0x3
}
+ public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+ }
+
+ public static class RaEvent.Builder {
+ ctor public RaEvent.Builder();
+ method public android.net.metrics.RaEvent build();
+ method public android.net.metrics.RaEvent.Builder updateDnsslLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixPreferredLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updatePrefixValidLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRdnssLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouteInfoLifetime(long);
+ method public android.net.metrics.RaEvent.Builder updateRouterLifetime(long);
+ }
+
public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
method public static String getProbeName(int);
field public static final int DNS_FAILURE = 0; // 0x0
@@ -1273,15 +1299,11 @@
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
method public abstract long getDuration();
method protected static int scale(int, float, int);
- field public static final int EFFECT_CLICK = 0; // 0x0
- field public static final int EFFECT_DOUBLE_CLICK = 1; // 0x1
- field public static final int EFFECT_HEAVY_CLICK = 5; // 0x5
field public static final int EFFECT_POP = 4; // 0x4
field public static final int EFFECT_STRENGTH_LIGHT = 0; // 0x0
field public static final int EFFECT_STRENGTH_MEDIUM = 1; // 0x1
field public static final int EFFECT_STRENGTH_STRONG = 2; // 0x2
field public static final int EFFECT_THUD = 3; // 0x3
- field public static final int EFFECT_TICK = 2; // 0x2
field public static final int[] RINGTONES;
}
@@ -1725,84 +1747,14 @@
package android.service.notification {
- public final class Adjustment implements android.os.Parcelable {
- ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int);
- ctor protected Adjustment(android.os.Parcel);
- method public int describeContents();
- method public CharSequence getExplanation();
- method public String getKey();
- method public String getPackage();
- method public android.os.Bundle getSignals();
- method public int getUser();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
- field public static final String KEY_IMPORTANCE = "key_importance";
- field public static final String KEY_PEOPLE = "key_people";
- field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
- field public static final String KEY_SMART_REPLIES = "key_smart_replies";
- field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
- field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
- }
-
@Deprecated public abstract class ConditionProviderService extends android.app.Service {
method @Deprecated public boolean isBound();
}
- public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
- ctor public NotificationAssistantService();
- method public final void adjustNotification(android.service.notification.Adjustment);
- method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
- method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
- method public final android.os.IBinder onBind(android.content.Intent);
- method public void onNotificationDirectReplied(@NonNull String);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
- method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel);
- method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
- method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String);
- method public void onNotificationsSeen(java.util.List<java.lang.String>);
- method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
- method public final void unsnoozeNotification(String);
- field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
- field public static final int SOURCE_FROM_APP = 0; // 0x0
- field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1
- }
-
public abstract class NotificationListenerService extends android.app.Service {
method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
}
- public final class NotificationStats implements android.os.Parcelable {
- ctor public NotificationStats();
- ctor protected NotificationStats(android.os.Parcel);
- method public int describeContents();
- method public int getDismissalSentiment();
- method public int getDismissalSurface();
- method public boolean hasDirectReplied();
- method public boolean hasExpanded();
- method public boolean hasInteracted();
- method public boolean hasSeen();
- method public boolean hasSnoozed();
- method public boolean hasViewedSettings();
- method public void setDirectReplied();
- method public void setDismissalSentiment(int);
- method public void setDismissalSurface(int);
- method public void setExpanded();
- method public void setSeen();
- method public void setSnoozed();
- method public void setViewedSettings();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
- field public static final int DISMISSAL_AOD = 2; // 0x2
- field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
- field public static final int DISMISSAL_OTHER = 0; // 0x0
- field public static final int DISMISSAL_PEEK = 1; // 0x1
- field public static final int DISMISSAL_SHADE = 3; // 0x3
- field public static final int DISMISS_SENTIMENT_NEGATIVE = 0; // 0x0
- field public static final int DISMISS_SENTIMENT_NEUTRAL = 1; // 0x1
- field public static final int DISMISS_SENTIMENT_POSITIVE = 2; // 0x2
- field public static final int DISMISS_SENTIMENT_UNKNOWN = -1000; // 0xfffffc18
- }
-
public final class SnoozeCriterion implements android.os.Parcelable {
ctor public SnoozeCriterion(String, CharSequence, CharSequence);
ctor protected SnoozeCriterion(android.os.Parcel);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 59b2aa6..ca10482 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -74,7 +74,6 @@
"src/external/SubsystemSleepStatePuller.cpp",
"src/external/PowerStatsPuller.cpp",
"src/external/ResourceHealthManagerPuller.cpp",
- "src/external/ResourceThermalManagerPuller.cpp",
"src/external/StatsPullerManager.cpp",
"src/external/puller_util.cpp",
"src/logd/LogEvent.cpp",
@@ -136,7 +135,6 @@
"android.hardware.power@1.0",
"android.hardware.power@1.1",
"android.hardware.power.stats@1.0",
- "android.hardware.thermal@2.0",
"libpackagelistparser",
"libsysutils",
"libcutils",
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 4434e07..9c320d3 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1105,6 +1105,22 @@
return hardware::Void();
}
+hardware::Return<void> StatsService::reportVendorAtom(const VendorAtom& vendorAtom) {
+ std::string reverseDomainName = (std::string) vendorAtom.reverseDomainName;
+ if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
+ ALOGE("Atom ID %ld is not a valid vendor atom ID", (long) vendorAtom.atomId);
+ return hardware::Void();
+ }
+ if (reverseDomainName.length() > 50) {
+ ALOGE("Vendor atom reverse domain name %s is too long.", reverseDomainName.c_str());
+ return hardware::Void();
+ }
+ LogEvent event(getWallClockSec() * NS_PER_SEC, getElapsedRealtimeNs(), vendorAtom);
+ mProcessor->OnLogEvent(&event);
+
+ return hardware::Void();
+}
+
void StatsService::binderDied(const wp <IBinder>& who) {
ALOGW("statscompanion service died");
StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 04b5a11..fe0504f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -211,6 +211,11 @@
virtual Return<void> reportSpeechDspStat(
const SpeechDspStat& speechDspStat) override;
+ /**
+ * Binder call to get vendor atom.
+ */
+ virtual Return<void> reportVendorAtom(const VendorAtom& vendorAtom) override;
+
/** IBinder::DeathRecipient */
virtual void binderDied(const wp<IBinder>& who) override;
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 21b03c4..8fb01b4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3021,8 +3021,8 @@
/**
* Pulls battery coulomb counter, which is the remaining battery charge in uAh.
- * Pulled from:
- * frameworks/base/cmds/statsd/src/external/ResourceHealthManagerPuller.cpp
+ *
+ * Pulled from StatsCompanionService.java
*/
message RemainingBatteryCapacity {
optional int32 charge_micro_ampere_hour = 1;
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp b/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
deleted file mode 100644
index 53709f1..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false // STOPSHIP if true
-#include "Log.h"
-
-#include <android/hardware/thermal/2.0/IThermal.h>
-#include "external/ResourceThermalManagerPuller.h"
-#include "external/StatsPuller.h"
-
-#include "ResourceThermalManagerPuller.h"
-#include "logd/LogEvent.h"
-#include "statslog.h"
-#include "stats_log_util.h"
-
-#include <chrono>
-
-using android::hardware::hidl_death_recipient;
-using android::hardware::hidl_vec;
-using android::hidl::base::V1_0::IBase;
-using ::android::hardware::thermal::V2_0::IThermal;
-using ::android::hardware::thermal::V2_0::Temperature;
-using ::android::hardware::thermal::V2_0::TemperatureType;
-using ::android::hardware::thermal::V1_0::ThermalStatus;
-using ::android::hardware::thermal::V1_0::ThermalStatusCode;
-using android::hardware::Return;
-using android::hardware::Void;
-
-using std::chrono::duration_cast;
-using std::chrono::nanoseconds;
-using std::chrono::system_clock;
-using std::make_shared;
-using std::shared_ptr;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-bool getThermalHalLocked();
-sp<android::hardware::thermal::V2_0::IThermal> gThermalHal = nullptr;
-std::mutex gThermalHalMutex;
-
-struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
- virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- ALOGE("ThermalHAL just died");
- gThermalHal = nullptr;
- getThermalHalLocked();
- }
-};
-
-sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
-
-// The caller must be holding gThermalHalMutex.
-bool getThermalHalLocked() {
- if (gThermalHal == nullptr) {
- gThermalHal = IThermal::getService();
- if (gThermalHal == nullptr) {
- ALOGE("Unable to get Thermal service.");
- } else {
- if (gThermalHalDeathRecipient == nullptr) {
- gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
- }
- hardware::Return<bool> linked = gThermalHal->linkToDeath(
- gThermalHalDeathRecipient, 0x451F /* cookie */);
- if (!linked.isOk()) {
- ALOGE("Transaction error in linking to ThermalHAL death: %s",
- linked.description().c_str());
- gThermalHal = nullptr;
- } else if (!linked) {
- ALOGW("Unable to link to ThermalHal death notifications");
- gThermalHal = nullptr;
- } else {
- ALOGD("Link to death notification successful");
- }
- }
- }
- return gThermalHal != nullptr;
-}
-
-ResourceThermalManagerPuller::ResourceThermalManagerPuller() :
- StatsPuller(android::util::TEMPERATURE) {
-}
-
-bool ResourceThermalManagerPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
- std::lock_guard<std::mutex> lock(gThermalHalMutex);
- if (!getThermalHalLocked()) {
- ALOGE("Thermal Hal not loaded");
- return false;
- }
-
- int64_t wallClockTimestampNs = getWallClockNs();
- int64_t elapsedTimestampNs = getElapsedRealtimeNs();
-
- data->clear();
- bool resultSuccess = true;
-
- Return<void> ret = gThermalHal->getCurrentTemperatures(false, TemperatureType::SKIN,
- [&](ThermalStatus status, const hidl_vec<Temperature>& temps) {
- if (status.code != ThermalStatusCode::SUCCESS) {
- ALOGE("Failed to get temperatures from ThermalHAL. Status: %d", status.code);
- resultSuccess = false;
- return;
- }
- if (mTagId == android::util::TEMPERATURE) {
- for (size_t i = 0; i < temps.size(); ++i) {
- auto ptr = make_shared<LogEvent>(android::util::TEMPERATURE,
- wallClockTimestampNs, elapsedTimestampNs);
- ptr->write((static_cast<int>(temps[i].type)));
- ptr->write(temps[i].name);
- // Convert the temperature to an int.
- int32_t temp = static_cast<int>(temps[i].value * 10);
- ptr->write(temp);
- ptr->init();
- data->push_back(ptr);
- }
- } else {
- ALOGE("Unsupported tag in ResourceThermalManagerPuller: %d", mTagId);
- }
- });
- if (!ret.isOk()) {
- ALOGE("getThermalHalLocked() failed: thermal HAL service not available. Description: %s",
- ret.description().c_str());
- if (ret.isDeadObject()) {
- gThermalHal = nullptr;
- }
- return false;
- }
- return resultSuccess;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/external/ResourceThermalManagerPuller.h b/cmds/statsd/src/external/ResourceThermalManagerPuller.h
deleted file mode 100644
index 5313792..0000000
--- a/cmds/statsd/src/external/ResourceThermalManagerPuller.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <utils/String16.h>
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Reads IThermal.hal
- */
-class ResourceThermalManagerPuller : public StatsPuller {
-public:
- ResourceThermalManagerPuller();
-
-private:
- bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 19a7389..7e56bee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -28,7 +28,6 @@
#include "../statscompanion_util.h"
#include "PowerStatsPuller.h"
#include "ResourceHealthManagerPuller.h"
-#include "ResourceThermalManagerPuller.h"
#include "StatsCompanionServicePuller.h"
#include "StatsPullerManager.h"
#include "SubsystemSleepStatePuller.h"
@@ -150,7 +149,8 @@
.puller =
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
// temperature
- {android::util::TEMPERATURE, {.puller = new ResourceThermalManagerPuller()}},
+ {android::util::TEMPERATURE,
+ {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
// binder_calls
{android::util::BINDER_CALLS,
{.additiveFields = {4, 5, 6, 8, 12},
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 192ce19..eaba9be 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -310,6 +310,36 @@
Value(usbPortOverheatEvent.timeToInactive)));
}
+LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const VendorAtom& vendorAtom) {
+ mLogdTimestampNs = wallClockTimestampNs;
+ mElapsedTimestampNs = elapsedTimestampNs;
+ mTagId = vendorAtom.atomId;
+
+ mValues.push_back(
+ FieldValue(Field(mTagId, getSimpleField(1)), Value(vendorAtom.reverseDomainName)));
+ for (int i = 0; i < (int)vendorAtom.values.size(); i++) {
+ switch (vendorAtom.values[i].getDiscriminator()) {
+ case VendorAtom::Value::hidl_discriminator::intValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].intValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::longValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].longValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::floatValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].floatValue())));
+ break;
+ case VendorAtom::Value::hidl_discriminator::stringValue:
+ mValues.push_back(FieldValue(Field(mTagId, getSimpleField(i + 2)),
+ Value(vendorAtom.values[i].stringValue())));
+ break;
+ }
+ }
+}
+
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) : LogEvent(tagId, timestampNs, 0) {}
LogEvent::LogEvent(int32_t tagId, int64_t timestampNs, int32_t uid) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 7199d6e..784376a 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -124,6 +124,9 @@
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const SpeechDspStat& speechDspStat);
+ explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+ const VendorAtom& vendorAtom);
+
~LogEvent();
/**
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index e9c43cd..5cf012638 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -548,6 +548,7 @@
{"AID_LMKD", 1069},
{"AID_LLKD", 1070},
{"AID_IORAPD", 1071},
+ {"AID_NETWORK_STACK", 1073},
{"AID_SHELL", 2000},
{"AID_CACHE", 2001},
{"AID_DIAG", 2002}};
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index de818a8..d9aba61 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -55,7 +55,8 @@
"AID_ROOT",
"AID_BLUETOOTH",
"AID_LMKD",
- "com.android.managedprovisioning"
+ "com.android.managedprovisioning",
+ "AID_NETWORK_STACK"
};
private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index fc47f67..ee2fe8f 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -3518,7 +3518,7 @@
Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object;
Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V
-Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V
+Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3243a5..ee3d27c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -77,9 +77,7 @@
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
-import android.net.Network;
import android.net.Proxy;
-import android.net.ProxyInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -1033,15 +1031,10 @@
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
}
- public void setHttpProxy(String host, String port, String exclList, Uri pacFileUrl) {
+ public void updateHttpProxy() {
final ConnectivityManager cm = ConnectivityManager.from(
getApplication() != null ? getApplication() : getSystemContext());
- final Network network = cm.getBoundNetworkForProcess();
- if (network != null) {
- Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
- } else {
- Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
- }
+ Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
}
public void processInBackground() {
@@ -5960,8 +5953,7 @@
// crash if we can't get it.
final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
try {
- final ProxyInfo proxyInfo = service.getProxyForNetwork(null);
- Proxy.setHttpProxySystemProperty(proxyInfo);
+ Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
} catch (RemoteException e) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index fcb6c14..c64fcf3 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -100,8 +100,7 @@
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
void clearDnsCache();
- void setHttpProxy(in String proxy, in String port, in String exclList,
- in Uri pacFileUrl);
+ void updateHttpProxy();
void setCoreSettings(in Bundle coreSettings);
void updatePackageCompatibilityInfo(in String pkg, in CompatibilityInfo info);
void scheduleTrimMemory(int level);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 75c9054..181acce 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -101,6 +101,12 @@
public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
/**
+ * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
+ * @hide
+ */
+ public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
+
+ /**
* A CharSequence description to show to the user on the alternate button when used with
* {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
* @hide
@@ -123,14 +129,39 @@
* {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
*
+ * @param title Title to be shown on the dialog.
+ * @param description Description to be shown on the dialog.
* @return the intent for launching the activity or null if no password is required.
**/
@RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
- if (!isDeviceSecure()) return null;
+ return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
+ }
+
+ /**
+ * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+ * for the current user of the device. The caller is expected to launch this activity using
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+ *
+ * @param title Title to be shown on the dialog.
+ * @param description Description to be shown on the dialog.
+ * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
+ * {@link android.hardware.biometrics.BiometricPrompt} with
+ * {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
+ * boolean)} set to false.
+ * @return the intent for launching the activity or null if no password is required.
+ * @hide
+ */
+ public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
+ boolean useImplicit) {
+ if (!isDeviceSecure()) {
+ return null;
+ }
Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
intent.putExtra(EXTRA_TITLE, title);
intent.putExtra(EXTRA_DESCRIPTION, description);
+ intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
// explicitly set the package for security
intent.setPackage(getSettingsPackageForIntent(intent));
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 43614fe..c4b4b40 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1137,9 +1137,6 @@
}
}
- /**
- * @hide
- */
public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55a3acb..ae756d4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10428,23 +10428,28 @@
}
/**
- * Whitelists a package that is allowed to access cross profile calendar APIs.
+ * Whitelists a set of packages that are allowed to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
+ * <p>Calling with a null value for the set disables the restriction so that all packages
+ * are allowed to access cross-profile calendar APIs. Calling with an empty set disallows
+ * all packages from accessing cross-profile calendar APIs. If this method isn't called,
+ * no package will be allowed to access cross-profile calendar APIs by default.
+ *
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be whitelisted.
+ * @param packageNames set of packages to be whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
* @see #getCrossProfileCalendarPackages(ComponentName)
*/
- public void addCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("addCrossProfileCalendarPackage");
+ public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
+ @Nullable Set<String> packageNames) {
+ throwIfParentInstance("setCrossProfileCalendarPackages");
if (mService != null) {
try {
- mService.addCrossProfileCalendarPackage(admin, packageName);
+ mService.setCrossProfileCalendarPackages(admin, packageNames == null ? null
+ : new ArrayList<>(packageNames));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10452,52 +10457,24 @@
}
/**
- * Removes a package that was allowed to access cross profile calendar APIs
- * from the whitelist.
- *
- * <p>Called by a profile owner of a managed profile.
- *
- * @param admin which {@link DeviceAdminReceiver} this request is associated with.
- * @param packageName name of the package to be removed from the whitelist.
- * @return {@code true} if the package is successfully removed from the whitelist,
- * {@code false} otherwise.
- * @throws SecurityException if {@code admin} is not a profile owner.
- *
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #getCrossProfileCalendarPackages(ComponentName)
- */
- public boolean removeCrossProfileCalendarPackage(@NonNull ComponentName admin,
- @NonNull String packageName) {
- throwIfParentInstance("removeCrossProfileCalendarPackage");
- if (mService != null) {
- try {
- return mService.removeCrossProfileCalendarPackage(admin, packageName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return false;
- }
-
- /**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>Called by a profile owner of a managed profile.
*
* @param admin which {@link DeviceAdminReceiver} this request is associated with.
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
* @throws SecurityException if {@code admin} is not a profile owner.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
+ public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackages(admin));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackages(admin);
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -10506,22 +10483,21 @@
}
/**
- * Returns if a package is whitelisted to access cross profile calendar APIs.
+ * Returns if a package is whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @param packageName the name of the package
- * @return {@code true} if the package is whitelisted to access cross profile calendar APIs.
+ * @return {@code true} if the package is whitelisted to access cross-profile calendar APIs.
* {@code false} otherwise.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
+ public boolean isPackageAllowedToAccessCalendar(@NonNull String packageName) {
throwIfParentInstance("isPackageAllowedToAccessCalendar");
if (mService != null) {
try {
@@ -10535,27 +10511,27 @@
}
/**
- * Gets a set of package names that are whitelisted to access cross profile calendar APIs.
+ * Gets a set of package names that are whitelisted to access cross-profile calendar APIs.
*
* <p>To query for a specific user, use
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} to create a context for
* that user, and get a {@link DevicePolicyManager} from this context.
*
* @return the set of names of packages that were previously whitelisted via
- * {@link #addCrossProfileCalendarPackage(ComponentName, String)}, or an
+ * {@link #setCrossProfileCalendarPackages(ComponentName, Set)}, or an
* empty set if none have been whitelisted.
*
- * @see #addCrossProfileCalendarPackage(ComponentName, String)
- * @see #removeCrossProfileCalendarPackage(ComponentName, String)
+ * @see #setCrossProfileCalendarPackages(ComponentName, Set)
* @see #getCrossProfileCalendarPackages(ComponentName)
* @hide
*/
- public @NonNull Set<String> getCrossProfileCalendarPackages() {
+ public @Nullable Set<String> getCrossProfileCalendarPackages() {
throwIfParentInstance("getCrossProfileCalendarPackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getCrossProfileCalendarPackagesForUser(
- myUserId()));
+ final List<String> packageNames = mService.getCrossProfileCalendarPackagesForUser(
+ myUserId());
+ return packageNames == null ? null : new ArraySet<>(packageNames);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 568becf..1751a91c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -424,8 +424,7 @@
void installUpdateFromFile(in ComponentName admin, in ParcelFileDescriptor updateFileDescriptor, in StartInstallingUpdateCallback listener);
- void addCrossProfileCalendarPackage(in ComponentName admin, String packageName);
- boolean removeCrossProfileCalendarPackage(in ComponentName admin, String packageName);
+ void setCrossProfileCalendarPackages(in ComponentName admin, in List<String> packageNames);
List<String> getCrossProfileCalendarPackages(in ComponentName admin);
boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index d2934b9..b1500c1 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -55,6 +55,9 @@
long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
in PendingIntent sessionEndCallbackIntent, String callingPackage);
void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
+ void registerAppUsageLimitObserver(int observerId, in String[] packages, long timeLimitMs,
+ in PendingIntent callback, String callingPackage);
+ void unregisterAppUsageLimitObserver(int observerId, String callingPackage);
void reportUsageStart(in IBinder activity, String token, String callingPackage);
void reportPastUsageStart(in IBinder activity, String token, long timeAgoMs,
String callingPackage);
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index d2de887..51397a2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -619,7 +619,7 @@
* @param timeLimit The total time the set of apps can be in the foreground before the
* callbackIntent is delivered. Must be at least one minute.
* @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
- * @param callbackIntent The PendingIntent that will be dispatched when the time limit is
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
* exceeded by the group of apps. The delivered Intent will also contain
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
* {@link #EXTRA_TIME_USED}. Cannot be null.
@@ -682,14 +682,14 @@
* @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
* Cannot be null.
* @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * time limit is exceeded by the group of apps. The delivered
- * Intent will also contain the extras {@link
+ * usage limit is exceeded by the group of apps. The
+ * delivered Intent will also contain the extras {@link
* #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
* #EXTRA_TIME_USED}. Cannot be null.
* @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the
- * session has ended after the time limit has been exceeded. The
- * session is considered at its end after the {@code observed}
- * usage has stopped and an additional {@code
+ * session has ended after the usage limit has been exceeded.
+ * The session is considered at its end after the {@code
+ * observed} usage has stopped and an additional {@code
* sessionThresholdTime} has passed. The delivered Intent will
* also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
* #EXTRA_TIME_USED}. Can be null.
@@ -736,6 +736,74 @@
}
/**
+ * Register a usage limit observer that receives a callback on the provided intent when the
+ * sum of usages of apps and tokens in the provided {@code observedEntities} array exceeds the
+ * {@code timeLimit} specified. The structure of a token is a {@link String} with the reporting
+ * package's name and a token that the calling app will use, separated by the forward slash
+ * character. Example: com.reporting.package/5OM3*0P4QU3-7OK3N
+ * <p>
+ * Registering an {@code observerId} that was already registered will override the previous one.
+ * No more than 1000 unique {@code observerId} may be registered by a single uid
+ * at any one time.
+ * A limit may be unregistered via {@link #unregisterAppUsageLimitObserver}
+ * <p>
+ * This method is similar to {@link #registerAppUsageObserver}, but the usage limit set here
+ * will be visible to the launcher so that it can report the limit to the user and how much
+ * of it is remaining.
+ * @see android.content.pm.LauncherApps#getAppUsageLimit
+ *
+ * @param observerId A unique id associated with the group of apps to be monitored. There can
+ * be multiple groups with common packages and different time limits.
+ * @param observedEntities The list of packages and token to observe for usage time. Cannot be
+ * null and must include at least one package or token.
+ * @param timeLimit The total time the set of apps can be in the foreground before the
+ * callbackIntent is delivered. Must be at least one minute.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param callbackIntent The PendingIntent that will be dispatched when the usage limit is
+ * exceeded by the group of apps. The delivered Intent will also contain
+ * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
+ * {@link #EXTRA_TIME_USED}. Cannot be null.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void registerAppUsageLimitObserver(int observerId, @NonNull String[] observedEntities,
+ long timeLimit, @NonNull TimeUnit timeUnit, @NonNull PendingIntent callbackIntent) {
+ try {
+ mService.registerAppUsageLimitObserver(observerId, observedEntities,
+ timeUnit.toMillis(timeLimit), callbackIntent, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister the app usage limit observer specified by the {@code observerId}.
+ * This will only apply to any observer registered by this application. Unregistering
+ * an observer that was already unregistered or never registered will have no effect.
+ *
+ * @param observerId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have both SUSPEND_APPS and OBSERVE_APP_USAGE
+ * permissions.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.SUSPEND_APPS,
+ android.Manifest.permission.OBSERVE_APP_USAGE})
+ public void unregisterAppUsageLimitObserver(int observerId) {
+ try {
+ mService.unregisterAppUsageLimitObserver(observerId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Report usage associated with a particular {@code token} has started. Tokens are app defined
* strings used to represent usage of in-app features. Apps with the {@link
* android.Manifest.permission#OBSERVE_APP_USAGE} permission can register time limit observers
@@ -743,6 +811,7 @@
* and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
@@ -766,6 +835,7 @@
* {@code activity} and usage will be considered stopped if the activity stops or crashes.
* @see #registerAppUsageObserver
* @see #registerUsageSessionObserver
+ * @see #registerAppUsageLimitObserver
*
* @param activity The activity {@code token} is associated with.
* @param token The token to report usage against.
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index d2d0cf9c..3d3c03a 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -20,6 +20,7 @@
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.os.UserHandle;
import java.util.List;
import java.util.Set;
@@ -270,4 +271,40 @@
* @param userId which user the app is associated with
*/
public abstract void reportExemptedSyncStart(String packageName, @UserIdInt int userId);
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link UsageStatsManager#registerAppUsageLimitObserver}.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user associated with the limit
+ * @return an {@link AppUsageLimitData} object describing the app time limit containing
+ * the given package, with the smallest time remaining.
+ */
+ public abstract AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user);
+
+ /** A class which is used to share the usage limit data for an app or a group of apps. */
+ public static class AppUsageLimitData {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97bc079..ab8c196 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1900,6 +1900,20 @@
}
/**
+ * Return true if Hearing Aid Profile is supported.
+ *
+ * @return true if phone supports Hearing Aid Profile
+ */
+ private boolean isHearingAidProfileSupported() {
+ try {
+ return mManagerService.isHearingAidProfileSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+ return false;
+ }
+ }
+
+ /**
* Get the maximum number of connected audio devices.
*
* @return the maximum number of connected audio devices
@@ -2051,6 +2065,11 @@
supportedProfiles.add(i);
}
}
+ } else {
+ // Bluetooth is disabled. Just fill in known supported Profiles
+ if (isHearingAidProfileSupported()) {
+ supportedProfiles.add(BluetoothProfile.HEARING_AID);
+ }
}
}
} catch (RemoteException e) {
@@ -2468,15 +2487,16 @@
* Get the profile proxy object associated with the profile.
*
* <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
- * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
- * and to get the proxy object.
+ * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+ * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+ * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+ * proxy object.
*
* @param context Context of the application
* @param listener The service Listener for connection callbacks.
* @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
- * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
- * {@link BluetoothProfile#GATT_SERVER}.
+ * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+ * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
* @return true on success, false on error
*/
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -2525,8 +2545,11 @@
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
- BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
- return true;
+ if (isHearingAidProfileSupported()) {
+ BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+ return true;
+ }
+ return false;
} else {
return false;
}
@@ -3253,7 +3276,7 @@
* @hide
*/
@SystemApi
- public abstract class MetadataListener {
+ public abstract static class MetadataListener {
/**
* Callback triggered if the metadata of {@link BluetoothDevice} registered in
* {@link #registerMetadataListener}.
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 6ed7942..82cc1bc 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -39,15 +39,14 @@
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- * This class provides the public APIs to control the Bluetooth Hearing Aid
- * profile.
+ * This class provides the public APIs to control the Hearing Aid profile.
*
* <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
* the BluetoothHearingAid proxy object.
*
- * <p> Each method is protected with its appropriate permission.
- * @hide
+ * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
+ * method is protected with its appropriate permission.
*/
public final class BluetoothHearingAid implements BluetoothProfile {
private static final String TAG = "BluetoothHearingAid";
@@ -56,7 +55,8 @@
/**
* Intent used to broadcast the change in connection state of the Hearing Aid
- * profile.
+ * profile. Please note that in the binaural case, there will be two different LE devices for
+ * the left and right side and each device will have their own connection state changes.S
*
* <p>This intent will have 3 extras:
* <ul>
@@ -77,27 +77,6 @@
"android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
/**
- * Intent used to broadcast the change in the Playing state of the Hearing Aid
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
- * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PLAYING_STATE_CHANGED =
- "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED";
-
- /**
* Intent used to broadcast the selection of a connected device as active.
*
* <p>This intent will have one extra:
@@ -108,6 +87,8 @@
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage
@@ -115,32 +96,38 @@
"android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
/**
- * Hearing Aid device is streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+ * This device represents Left Hearing Aid.
+ *
+ * @hide
*/
- public static final int STATE_PLAYING = 10;
-
- /**
- * Hearing Aid device is NOT streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_NOT_PLAYING = 11;
-
- /** This device represents Left Hearing Aid. */
public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
- /** This device represents Right Hearing Aid. */
+ /**
+ * This device represents Right Hearing Aid.
+ *
+ * @hide
+ */
public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
- /** This device is Monaural. */
+ /**
+ * This device is Monaural.
+ *
+ * @hide
+ */
public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
- /** This device is Binaural (should receive only left or right audio). */
+ /**
+ * This device is Binaural (should receive only left or right audio).
+ *
+ * @hide
+ */
public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
- /** Can't read ClientID for this device */
+ /**
+ * Indicates the HiSyncID could not be read and is unavailable.
+ *
+ * @hide
+ */
public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
private Context mContext;
@@ -236,12 +223,6 @@
}
}
- @Override
- public void finalize() {
- // The empty finalize needs to be kept or the
- // cts signature tests would fail.
- }
-
/**
* Initiate connection to a profile of the remote bluetooth device.
*
@@ -538,10 +519,6 @@
return "connected";
case STATE_DISCONNECTING:
return "disconnecting";
- case STATE_PLAYING:
- return "playing";
- case STATE_NOT_PLAYING:
- return "not playing";
default:
return "<unknown state " + state + ">";
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c87c73..b8670db 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -185,7 +185,6 @@
/**
* Hearing Aid Device
*
- * @hide
*/
int HEARING_AID = 21;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 3cfbe0c..47034a6 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -508,7 +508,7 @@
/**
* Bit in {@link #privateFlags} indicating if the activity should be shown when locked in case
* an activity behind this can also be shown when locked.
- * See android.R.attr#inheritShowWhenLocked
+ * See {@link android.R.attr#inheritShowWhenLocked}.
* @hide
*/
public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 576466f..5d6d144 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1955,6 +1955,11 @@
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
}
+ /** @hide */
+ public boolean isCodeIntegrityPreferred() {
+ return (privateFlags & PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0;
+ }
+
/**
* Returns whether or not this application was installed as a virtual preload.
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index db2b6fd..d1bc377 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -23,6 +23,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.LauncherApps;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -56,6 +57,9 @@
ApplicationInfo getApplicationInfo(
String callingPackage, String packageName, int flags, in UserHandle user);
+ LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage, String packageName,
+ in UserHandle user);
+
ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
diff --git a/core/java/android/content/pm/LauncherApps.aidl b/core/java/android/content/pm/LauncherApps.aidl
new file mode 100644
index 0000000..1d98ad1
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, 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.pm;
+
+parcelable LauncherApps.AppUsageLimit;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 766c566..89630e1 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -758,6 +758,27 @@
}
/**
+ * Returns an object describing the app usage limit for the given package.
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ *
+ * @param packageName name of the package whose app usage limit will be returned
+ * @param user the user of the package
+ *
+ * @return an {@link AppUsageLimit} object describing the app time limit containing
+ * the given package with the smallest time remaining, or {@code null} if none exist.
+ * @throws SecurityException when the caller is not the active launcher.
+ */
+ @Nullable
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String packageName, UserHandle user) {
+ try {
+ return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if the activity exists and it enabled for a profile.
*
* @param component The activity to check.
@@ -1632,4 +1653,86 @@
return 0;
}
}
+
+ /**
+ * A class that encapsulates information about the usage limit set for an app or
+ * a group of apps.
+ *
+ * <p>The launcher can query specifics about the usage limit such as if it is a group limit,
+ * how much usage time the limit has, and how much of the total usage time is remaining
+ * via the APIs available in this class.
+ *
+ * @see #getAppUsageLimit(String, UserHandle)
+ */
+ public static final class AppUsageLimit implements Parcelable {
+ private final boolean mGroupLimit;
+ private final long mTotalUsageLimit;
+ private final long mUsageRemaining;
+
+ /** @hide */
+ public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
+ this.mGroupLimit = groupLimit;
+ this.mTotalUsageLimit = totalUsageLimit;
+ this.mUsageRemaining = usageRemaining;
+ }
+
+ /**
+ * Returns whether this limit refers to a group of apps.
+ *
+ * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise.
+ * @hide
+ */
+ public boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ /**
+ * Returns the total usage limit in milliseconds set for an app or a group of apps.
+ *
+ * @return the total usage limit in milliseconds
+ */
+ public long getTotalUsageLimit() {
+ return mTotalUsageLimit;
+ }
+
+ /**
+ * Returns the usage remaining in milliseconds for an app or the group of apps
+ * this limit refers to.
+ *
+ * @return the usage remaining in milliseconds
+ */
+ public long getUsageRemaining() {
+ return mUsageRemaining;
+ }
+
+ private AppUsageLimit(Parcel source) {
+ mGroupLimit = source.readBoolean();
+ mTotalUsageLimit = source.readLong();
+ mUsageRemaining = source.readLong();
+ }
+
+ public static final Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
+ @Override
+ public AppUsageLimit createFromParcel(Parcel source) {
+ return new AppUsageLimit(source);
+ }
+
+ @Override
+ public AppUsageLimit[] newArray(int size) {
+ return new AppUsageLimit[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mGroupLimit);
+ dest.writeLong(mTotalUsageLimit);
+ dest.writeLong(mUsageRemaining);
+ }
+ }
}
diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
index 81e4105..7790067 100644
--- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
+++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java
@@ -25,12 +25,6 @@
* Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is
* included by default.
*
- * <p>This is separated out so that it can be conditionally included at build time depending on
- * whether org.apache.http.legacy is on the bootclasspath or not. In order to include this at
- * build time, and remove org.apache.http.legacy from the bootclasspath pass
- * REMOVE_OAHL_FROM_BCP=true on the build command line, otherwise this class will not be included
- * and the
- *
* @hide
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 03eefed..b19196a 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -45,13 +45,9 @@
static {
final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>();
- // Attempt to load and add the optional updater that will only be available when
- // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that
- // will remove any references to org.apache.http.library from the package so that it does
- // not try and load the library when it is on the bootclasspath.
- boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters,
- "android.content.pm.OrgApacheHttpLegacyUpdater",
- RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new);
+ // Automatically add the org.apache.http.legacy library to the app classpath if the app
+ // targets < P.
+ packageUpdaters.add(new OrgApacheHttpLegacyUpdater());
packageUpdaters.add(new AndroidHidlUpdater());
@@ -70,7 +66,7 @@
PackageSharedLibraryUpdater[] updaterArray = packageUpdaters
.toArray(new PackageSharedLibraryUpdater[0]);
INSTANCE = new PackageBackwardCompatibility(
- bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
+ bootClassPathContainsATB, updaterArray);
}
/**
@@ -116,15 +112,12 @@
return INSTANCE;
}
- private final boolean mBootClassPathContainsOAHL;
-
private final boolean mBootClassPathContainsATB;
private final PackageSharedLibraryUpdater[] mPackageUpdaters;
- public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
+ public PackageBackwardCompatibility(
boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
- this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
this.mBootClassPathContainsATB = bootClassPathContainsATB;
this.mPackageUpdaters = packageUpdaters;
}
@@ -148,14 +141,6 @@
}
/**
- * True if the org.apache.http.legacy is on the bootclasspath, false otherwise.
- */
- @VisibleForTesting
- public static boolean bootClassPathContainsOAHL() {
- return INSTANCE.mBootClassPathContainsOAHL;
- }
-
- /**
* True if the android.test.base is on the bootclasspath, false otherwise.
*/
@VisibleForTesting
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 94b7c45..73b1f4e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1543,6 +1543,13 @@
this.isStaged = true;
}
+ /**
+ * Set this session to be installing an APEX package.
+ */
+ public void setInstallAsApex() {
+ installFlags |= PackageManager.INSTALL_APEX;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9e2f316..783ee64 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2223,6 +2223,13 @@
public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports attaching to IMS implementations using the ImsService API in telephony.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d5636d5..eb59cfc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8512,6 +8512,7 @@
collectCerts ? PackageParser.PARSE_COLLECT_CERTIFICATES : 0);
pi.packageName = apk.packageName;
+ ai.packageName = apk.packageName;
pi.setLongVersionCode(apk.getLongVersionCode());
ai.setVersionCode(apk.getLongVersionCode());
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 6e519c1..a8c3b88 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -475,14 +475,6 @@
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
- // if this package is not one of those changedUids, we don't need to scan it,
- // since nothing in it changed, so save a call to parseServiceInfo, which
- // can cause a large amount of the package apk to be loaded into memory.
- // if this is the initial scan, changedUids will be null, and containsUid will
- // trivially return true, and will call parseServiceInfo
- if (!containsUid(changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
- continue;
- }
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c809cca..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,6 +15,9 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@
import libcore.net.event.NetworkEventDispatcher;
+import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
@@ -66,6 +72,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@
* {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
* {@link PacketKeepaliveCallback#onError} if an error occurred.
*
+ * @deprecated Use {@link SocketKeepalive} instead.
+ *
* @hide
*/
public class PacketKeepalive {
@@ -1802,6 +1811,8 @@
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -1821,6 +1832,62 @@
}
/**
+ * Request that keepalives be started on a IPsec NAT-T socket.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
+ **/
+ public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull UdpEncapsulationSocket socket,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
+ socket.getResourceId(), source, destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+ * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
+ * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
+ * that port.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+ * keepalive packets will always be sent to port 4500 of the given {@code destination}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull FileDescriptor fd,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
+ source, destination, executor, callback);
+ }
+
+ /**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 131925e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -181,6 +181,10 @@
void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
in IBinder binder, String srcAddr, int srcPort, String dstAddr);
+ void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+ int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
+ String dstAddr);
+
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 4631c56..b996cda 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
@@ -83,6 +85,8 @@
* @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
* @hide
*/
+ @SystemApi
+ @TestApi
public IpPrefix(InetAddress address, int prefixLength) {
// We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
// which is unnecessary because getAddress() already returns a clone.
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index a536d08..fbd602c 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -162,6 +162,8 @@
* {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
* @hide
*/
+ @SystemApi
+ @TestApi
public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
init(address, prefixLength, flags, scope);
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 21b6a8e..6628701 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -174,7 +174,8 @@
/**
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public LinkProperties(LinkProperties source) {
if (source != null) {
mIfaceName = source.mIfaceName;
@@ -576,6 +577,8 @@
* @param addresses The {@link Collection} of PCSCF servers to set in this object.
* @hide
*/
+ @SystemApi
+ @TestApi
public void setPcscfServers(Collection<InetAddress> pcscfServers) {
mPcscfs.clear();
for (InetAddress pcscfServer: pcscfServers) {
@@ -590,6 +593,8 @@
* this link.
* @hide
*/
+ @SystemApi
+ @TestApi
public List<InetAddress> getPcscfServers() {
return Collections.unmodifiableList(mPcscfs);
}
@@ -781,6 +786,8 @@
* @return the NAT64 prefix.
* @hide
*/
+ @SystemApi
+ @TestApi
public @Nullable IpPrefix getNat64Prefix() {
return mNat64Prefix;
}
@@ -794,6 +801,8 @@
* @param prefix the NAT64 prefix.
* @hide
*/
+ @SystemApi
+ @TestApi
public void setNat64Prefix(IpPrefix prefix) {
if (prefix != null && prefix.getPrefixLength() != 96) {
throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..88631ae
--- /dev/null
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ @NonNull private final InetAddress mSource;
+ @NonNull private final InetAddress mDestination;
+ @NonNull private final FileDescriptor mFd;
+ private final int mResourceId;
+
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull FileDescriptor fd,
+ int resourceId,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, executor, callback);
+ mSource = source;
+ mDestination = destination;
+ mFd = fd;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ void startImpl(int intervalSec) {
+ try {
+ mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger,
+ new Binder(), mSource.getHostAddress(), mDestination.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+
+ @Override
+ void stopImpl() {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+}
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index e926fda..ef2269a 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -39,12 +39,12 @@
*/
public class ProxyInfo implements Parcelable {
- private String mHost;
- private int mPort;
- private String mExclusionList;
- private String[] mParsedExclusionList;
+ private final String mHost;
+ private final int mPort;
+ private final String mExclusionList;
+ private final String[] mParsedExclusionList;
+ private final Uri mPacFileUrl;
- private Uri mPacFileUrl;
/**
*@hide
*/
@@ -96,7 +96,8 @@
public ProxyInfo(String host, int port, String exclList) {
mHost = host;
mPort = port;
- setExclusionList(exclList);
+ mExclusionList = exclList;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.EMPTY;
}
@@ -107,7 +108,8 @@
public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
@@ -121,7 +123,8 @@
public ProxyInfo(String pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.parse(pacFileUrl);
}
@@ -132,13 +135,22 @@
public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST;
mPort = localProxyPort;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
mPacFileUrl = pacFileUrl;
}
+ private static String[] parseExclusionList(String exclusionList) {
+ if (exclusionList == null) {
+ return new String[0];
+ } else {
+ return exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
mHost = host;
mPort = port;
@@ -159,6 +171,10 @@
mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList;
} else {
+ mHost = null;
+ mPort = 0;
+ mExclusionList = null;
+ mParsedExclusionList = null;
mPacFileUrl = Uri.EMPTY;
}
}
@@ -214,24 +230,14 @@
return mExclusionList;
}
- // comma separated
- private void setExclusionList(String exclusionList) {
- mExclusionList = exclusionList;
- if (mExclusionList == null) {
- mParsedExclusionList = new String[0];
- } else {
- mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
- }
- }
-
/**
* @hide
*/
public boolean isValid() {
if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
- mPort == 0 ? "" : Integer.toString(mPort),
- mExclusionList == null ? "" : mExclusionList);
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
}
/**
@@ -262,7 +268,7 @@
sb.append("] ");
sb.append(Integer.toString(mPort));
if (mExclusionList != null) {
- sb.append(" xl=").append(mExclusionList);
+ sb.append(" xl=").append(mExclusionList);
}
} else {
sb.append("[ProxyProperties.mHost == null]");
@@ -308,8 +314,8 @@
*/
public int hashCode() {
return ((null == mHost) ? 0 : mHost.hashCode())
- + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
- + mPort;
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
}
/**
@@ -352,8 +358,7 @@
}
String exclList = in.readString();
String[] parsedExclList = in.readStringArray();
- ProxyInfo proxyProperties =
- new ProxyInfo(host, port, exclList, parsedExclList);
+ ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
return proxyProperties;
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 6bf2c67..5c0f758 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -110,6 +110,8 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
switch (type) {
case RTN_UNICAST:
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..97d50f4
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 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.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+ static final String TAG = "SocketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int DATA_RECEIVED = -2;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+ /** The target socket is invalid. */
+ public static final int ERROR_INVALID_SOCKET = -25;
+ /** The target socket is not idle. */
+ public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_NETWORK,
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH,
+ ERROR_INVALID_INTERVAL,
+ ERROR_INVALID_SOCKET,
+ ERROR_SOCKET_NOT_IDLE
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * The minimum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MIN_INTERVAL_SEC = 10;
+
+ /**
+ * The maximum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MAX_INTERVAL_SEC = 3600;
+
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull private final Executor mExecutor;
+ @NonNull private final SocketKeepalive.Callback mCallback;
+ @NonNull private final Looper mLooper;
+ @NonNull final Messenger mMessenger;
+ @NonNull Integer mSlot;
+
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mService = service;
+ mNetwork = network;
+ mExecutor = executor;
+ mCallback = callback;
+ // TODO: 1. Use other thread modeling instead of create one thread for every instance to
+ // reduce the memory cost.
+ // 2. support restart.
+ // 3. Fix race condition which caused by rapidly start and stop.
+ HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+ + Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ thread.start();
+ mLooper = thread.getLooper();
+ mMessenger = new Messenger(new Handler(mLooper) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+ final int status = message.arg2;
+ try {
+ if (status == SUCCESS) {
+ if (mSlot == null) {
+ mSlot = message.arg1;
+ mExecutor.execute(() -> mCallback.onStarted());
+ } else {
+ mSlot = null;
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onStopped());
+ }
+ } else if (status == DATA_RECEIVED) {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onDataReceived());
+ } else {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onError(status));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
+ }
+ break;
+ default:
+ Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}. See
+ * {@link SocketKeepalive}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds, otherwise
+ * {@link #ERROR_INVALID_INTERVAL} will be returned.
+ */
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec) {
+ startImpl(intervalSec);
+ }
+
+ abstract void startImpl(int intervalSec);
+
+ /** @hide */
+ protected void stopLooper() {
+ // TODO: remove this after changing thread modeling.
+ mLooper.quit();
+ }
+
+ /**
+ * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+ * before using the object. See {@link SocketKeepalive}.
+ */
+ public final void stop() {
+ stopImpl();
+ }
+
+ abstract void stopImpl();
+
+ /**
+ * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+ * usable again if {@code close()} is called.
+ */
+ @Override
+ public final void close() {
+ stop();
+ stopLooper();
+ }
+
+ /**
+ * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+ * {@link SocketKeepalive}.
+ */
+ public static class Callback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(@ErrorCode int error) {}
+ /** The keepalive on a TCP socket was stopped because the socket received data. */
+ public void onDataReceived() {}
+ }
+}
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 37bf3a7..dc099a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -509,6 +509,15 @@
}
/**
+ * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation
+ * and it is possible that some apps will ignore it.
+ */
+ public Builder setHttpProxy(ProxyInfo proxyInfo) {
+ mConfig.proxyInfo = proxyInfo;
+ return this;
+ }
+
+ /**
* Add a network address to the VPN interface. Both IPv4 and IPv6
* addresses are supported. At least one address must be set before
* calling {@link #establish}.
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 16aea31b..5b5a235 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -18,7 +18,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.Network;
@@ -51,7 +50,8 @@
public interface Event extends Parcelable {}
/** @hide */
- @UnsupportedAppUsage
+ @SystemApi
+ @TestApi
public IpConnectivityLog() {
}
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index d308246..04a2e6e 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -16,7 +16,8 @@
package android.net.metrics;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,19 +25,28 @@
* An event logged when the APF packet socket receives an RA packet.
* {@hide}
*/
+@SystemApi
+@TestApi
public final class RaEvent implements IpConnectivityLog.Event {
- public static final long NO_LIFETIME = -1L;
+ private static final long NO_LIFETIME = -1L;
// Lifetime in seconds of options found in a single RA packet.
// When an option is not set, the value of the associated field is -1;
+ /** @hide */
public final long routerLifetime;
+ /** @hide */
public final long prefixValidLifetime;
+ /** @hide */
public final long prefixPreferredLifetime;
+ /** @hide */
public final long routeInfoLifetime;
+ /** @hide */
public final long rdnssLifetime;
+ /** @hide */
public final long dnsslLifetime;
+ /** @hide */
public RaEvent(long routerLifetime, long prefixValidLifetime, long prefixPreferredLifetime,
long routeInfoLifetime, long rdnssLifetime, long dnsslLifetime) {
this.routerLifetime = routerLifetime;
@@ -47,6 +57,7 @@
this.dnsslLifetime = dnsslLifetime;
}
+ /** @hide */
private RaEvent(Parcel in) {
routerLifetime = in.readLong();
prefixValidLifetime = in.readLong();
@@ -56,6 +67,7 @@
dnsslLifetime = in.readLong();
}
+ /** @hide */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(routerLifetime);
@@ -66,6 +78,7 @@
out.writeLong(dnsslLifetime);
}
+ /** @hide */
@Override
public int describeContents() {
return 0;
@@ -83,6 +96,7 @@
.toString();
}
+ /** @hide */
public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
public RaEvent createFromParcel(Parcel in) {
return new RaEvent(in);
@@ -102,47 +116,39 @@
long rdnssLifetime = NO_LIFETIME;
long dnsslLifetime = NO_LIFETIME;
- @UnsupportedAppUsage
public Builder() {
}
- @UnsupportedAppUsage
public RaEvent build() {
return new RaEvent(routerLifetime, prefixValidLifetime, prefixPreferredLifetime,
routeInfoLifetime, rdnssLifetime, dnsslLifetime);
}
- @UnsupportedAppUsage
public Builder updateRouterLifetime(long lifetime) {
routerLifetime = updateLifetime(routerLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updatePrefixValidLifetime(long lifetime) {
prefixValidLifetime = updateLifetime(prefixValidLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updatePrefixPreferredLifetime(long lifetime) {
prefixPreferredLifetime = updateLifetime(prefixPreferredLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateRouteInfoLifetime(long lifetime) {
routeInfoLifetime = updateLifetime(routeInfoLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateRdnssLifetime(long lifetime) {
rdnssLifetime = updateLifetime(rdnssLifetime, lifetime);
return this;
}
- @UnsupportedAppUsage
public Builder updateDnsslLifetime(long lifetime) {
dnsslLifetime = updateLifetime(dnsslLifetime, lifetime);
return this;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 07c4933..d2ab053 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -530,7 +530,7 @@
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- packagesForUid, visibleVols, zygoteArgs);
+ packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs);
}
/** @hide */
@@ -551,7 +551,7 @@
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- packagesForUid, visibleVols, zygoteArgs);
+ packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 01d85c6..99fb608 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.ContentResolver;
@@ -25,6 +26,8 @@
import android.net.Uri;
import android.util.MathUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -52,26 +55,20 @@
* A click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_CLICK = Effect.CLICK;
/**
* A double click effect.
*
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
* A tick effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_TICK = Effect.TICK;
/**
@@ -93,9 +90,7 @@
/**
* A heavy click effect.
* @see #get(int)
- * @hide
*/
- @TestApi
public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
/** {@hide} */
@@ -136,6 +131,16 @@
Effect.RINGTONE_15
};
+ /** @hide */
+ @IntDef(prefix = { "EFFECT_" }, value = {
+ EFFECT_TICK,
+ EFFECT_CLICK,
+ EFFECT_HEAVY_CLICK,
+ EFFECT_DOUBLE_CLICK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EffectType {}
+
/** @hide to prevent subclassing from outside of the framework */
public VibrationEffect() { }
@@ -219,6 +224,27 @@
}
/**
+ * Create a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * This will fallback to a generic pattern if one exists and there does not exist a
+ * hardware-specific implementation of the effect.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}, {@link #EFFECT_TICK}
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createPrebaked(@EffectType int effectId) {
+ return get(effectId, true);
+ }
+
+ /**
* Get a predefined vibration effect.
*
* Predefined effects are a set of common vibration effects that should be identical, regardless
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 4cb9c5b..9e47179 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -76,6 +76,16 @@
/**
* @hide for internal use only
*/
+ public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";
+
+ /**
+ * @hide for internal use only
+ */
+ public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary";
+
+ /**
+ * @hide for internal use only
+ */
private static final String LOG_TAG = "ZygoteProcess";
/**
@@ -87,6 +97,15 @@
* The name of the secondary (alternate ABI) zygote socket.
*/
private final LocalSocketAddress mZygoteSecondarySocketAddress;
+ /**
+ * The name of the socket used to communicate with the primary blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSocketAddress;
+
+ /**
+ * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
+ */
+ private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;
public ZygoteProcess() {
mZygoteSocketAddress =
@@ -94,12 +113,22 @@
mZygoteSecondarySocketAddress =
new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
LocalSocketAddress.Namespace.RESERVED);
+
+ mBlastulaPoolSocketAddress =
+ new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
+ mBlastulaPoolSecondarySocketAddress =
+ new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME,
+ LocalSocketAddress.Namespace.RESERVED);
}
public ZygoteProcess(LocalSocketAddress primarySocketAddress,
LocalSocketAddress secondarySocketAddress) {
mZygoteSocketAddress = primarySocketAddress;
mZygoteSecondarySocketAddress = secondarySocketAddress;
+
+ mBlastulaPoolSocketAddress = null;
+ mBlastulaPoolSecondarySocketAddress = null;
}
public LocalSocketAddress getPrimarySocketAddress() {
@@ -111,6 +140,7 @@
*/
public static class ZygoteState {
final LocalSocketAddress mZygoteSocketAddress;
+ final LocalSocketAddress mBlastulaSocketAddress;
private final LocalSocket mZygoteSessionSocket;
@@ -122,11 +152,13 @@
private boolean mClosed;
private ZygoteState(LocalSocketAddress zygoteSocketAddress,
+ LocalSocketAddress blastulaSocketAddress,
LocalSocket zygoteSessionSocket,
DataInputStream zygoteInputStream,
BufferedWriter zygoteOutputWriter,
List<String> abiList) {
this.mZygoteSocketAddress = zygoteSocketAddress;
+ this.mBlastulaSocketAddress = blastulaSocketAddress;
this.mZygoteSessionSocket = zygoteSessionSocket;
this.mZygoteInputStream = zygoteInputStream;
this.mZygoteOutputWriter = zygoteOutputWriter;
@@ -134,14 +166,17 @@
}
/**
- * Create a new ZygoteState object by connecting to the given Zygote socket.
+ * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
+ * given blastula socket address.
*
* @param zygoteSocketAddress Zygote socket to connect to
+ * @param blastulaSocketAddress Blastula socket address to save for later
* @return A new ZygoteState object containing a session socket for the given Zygote socket
* address
* @throws IOException
*/
- public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
+ public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
+ LocalSocketAddress blastulaSocketAddress)
throws IOException {
DataInputStream zygoteInputStream = null;
@@ -154,7 +189,7 @@
zygoteOutputWriter =
new BufferedWriter(
new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
- 256);
+ Zygote.SOCKET_BUFFER_SIZE);
} catch (IOException ex) {
try {
zygoteSessionSocket.close();
@@ -163,11 +198,18 @@
throw ex;
}
- return new ZygoteState(zygoteSocketAddress,
+ return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
getAbiList(zygoteOutputWriter, zygoteInputStream));
}
+ LocalSocket getBlastulaSessionSocket() throws IOException {
+ final LocalSocket blastulaSessionSocket = new LocalSocket();
+ blastulaSessionSocket.connect(this.mBlastulaSocketAddress);
+
+ return blastulaSessionSocket;
+ }
+
boolean matches(String abi) {
return mABIList.contains(abi);
}
@@ -269,12 +311,13 @@
@Nullable String packageName,
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
+ boolean useBlastulaPool,
@Nullable String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
- packageName, packagesForUid, visibleVols, zygoteArgs);
+ packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -322,59 +365,128 @@
*/
@GuardedBy("mLock")
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
- ZygoteState zygoteState, ArrayList<String> args)
+ ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
throws ZygoteStartFailedEx {
- try {
- // Throw early if any of the arguments are malformed. This means we can
- // avoid writing a partial response to the zygote.
- int sz = args.size();
- for (int i = 0; i < sz; i++) {
- if (args.get(i).indexOf('\n') >= 0) {
- throw new ZygoteStartFailedEx("embedded newlines not allowed");
+ // Throw early if any of the arguments are malformed. This means we can
+ // avoid writing a partial response to the zygote.
+ for (String arg : args) {
+ if (arg.indexOf('\n') >= 0) {
+ throw new ZygoteStartFailedEx("embedded newlines not allowed");
+ }
+ }
+
+ /**
+ * See com.android.internal.os.SystemZygoteInit.readArgumentList()
+ * Presently the wire format to the zygote process is:
+ * a) a count of arguments (argc, in essence)
+ * b) a number of newline-separated argument strings equal to count
+ *
+ * After the zygote process reads these it will write the pid of
+ * the child or -1 on failure, followed by boolean to
+ * indicate whether a wrapper process was used.
+ */
+ String msgStr = Integer.toString(args.size()) + "\n"
+ + String.join("\n", args) + "\n";
+
+ // Should there be a timeout on this?
+ Process.ProcessStartResult result = new Process.ProcessStartResult();
+
+ // TODO (chriswailes): Move branch body into separate function.
+ if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) {
+ LocalSocket blastulaSessionSocket = null;
+
+ try {
+ blastulaSessionSocket = zygoteState.getBlastulaSessionSocket();
+
+ final BufferedWriter blastulaWriter =
+ new BufferedWriter(
+ new OutputStreamWriter(blastulaSessionSocket.getOutputStream()),
+ Zygote.SOCKET_BUFFER_SIZE);
+ final DataInputStream blastulaReader =
+ new DataInputStream(blastulaSessionSocket.getInputStream());
+
+ blastulaWriter.write(msgStr);
+ blastulaWriter.flush();
+
+ result.pid = blastulaReader.readInt();
+ // Blastulas can't be used to spawn processes that need wrappers.
+ result.usingWrapper = false;
+
+ if (result.pid < 0) {
+ throw new ZygoteStartFailedEx("Blastula specialization failed");
+ }
+
+ return result;
+ } catch (IOException ex) {
+ // If there was an IOException using the blastula pool we will log the error and
+ // attempt to start the process through the Zygote.
+ Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - "
+ + ex.toString());
+ } finally {
+ try {
+ blastulaSessionSocket.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage());
}
}
+ }
- /**
- * See com.android.internal.os.SystemZygoteInit.readArgumentList()
- * Presently the wire format to the zygote process is:
- * a) a count of arguments (argc, in essence)
- * b) a number of newline-separated argument strings equal to count
- *
- * After the zygote process reads these it will write the pid of
- * the child or -1 on failure, followed by boolean to
- * indicate whether a wrapper process was used.
- */
- final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
- final DataInputStream inputStream = zygoteState.mZygoteInputStream;
+ try {
+ final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+ final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
- writer.write(Integer.toString(args.size()));
- writer.newLine();
-
- for (int i = 0; i < sz; i++) {
- String arg = args.get(i);
- writer.write(arg);
- writer.newLine();
- }
-
- writer.flush();
-
- // Should there be a timeout on this?
- Process.ProcessStartResult result = new Process.ProcessStartResult();
+ zygoteWriter.write(msgStr);
+ zygoteWriter.flush();
// Always read the entire result from the input stream to avoid leaving
// bytes in the stream for future process starts to accidentally stumble
// upon.
- result.pid = inputStream.readInt();
- result.usingWrapper = inputStream.readBoolean();
-
- if (result.pid < 0) {
- throw new ZygoteStartFailedEx("fork() failed");
- }
- return result;
+ result.pid = zygoteInputStream.readInt();
+ result.usingWrapper = zygoteInputStream.readBoolean();
} catch (IOException ex) {
zygoteState.close();
+ Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
+ + ex.toString());
throw new ZygoteStartFailedEx(ex);
}
+
+ if (result.pid < 0) {
+ throw new ZygoteStartFailedEx("fork() failed");
+ }
+
+ return result;
+ }
+
+ /**
+ * Flags that may not be passed to a blastula.
+ */
+ private static final String[] INVALID_BLASTULA_FLAGS = {
+ "--query-abi-list",
+ "--get-pid",
+ "--preload-default",
+ "--preload-package",
+ "--preload-app",
+ "--start-child-zygote",
+ "--set-api-blacklist-exemptions",
+ "--hidden-api-log-sampling-rate",
+ "--invoke-with"
+ };
+
+ /**
+ * Tests a command list to see if it is valid to send to a blastula.
+ * @param args Zygote/Blastula command arguments
+ * @return True if the command can be passed to a blastula; false otherwise
+ */
+ private static boolean isValidBlastulaCommand(ArrayList<String> args) {
+ for (String flag : args) {
+ for (String badFlag : INVALID_BLASTULA_FLAGS) {
+ if (flag.startsWith(badFlag)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
/**
@@ -416,6 +528,7 @@
@Nullable String packageName,
@Nullable String[] packagesForUid,
@Nullable String[] visibleVols,
+ boolean useBlastulaPool,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -522,7 +635,9 @@
}
synchronized(mLock) {
- return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+ return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+ useBlastulaPool,
+ argsForZygote);
}
}
@@ -686,7 +801,7 @@
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState =
- ZygoteState.connect(mZygoteSocketAddress);
+ ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
}
@@ -703,7 +818,8 @@
if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
try {
secondaryZygoteState =
- ZygoteState.connect(mZygoteSecondarySocketAddress);
+ ZygoteState.connect(mZygoteSecondarySocketAddress,
+ mBlastulaPoolSecondarySocketAddress);
} catch (IOException ioe) {
throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
}
@@ -820,7 +936,7 @@
for (int n = 20; n >= 0; n--) {
try {
final ZygoteState zs =
- ZygoteState.connect(zygoteSocketAddress);
+ ZygoteState.connect(zygoteSocketAddress, null);
zs.close();
return;
} catch (IOException ioe) {
@@ -884,7 +1000,8 @@
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
- null /* packagesForUid */, null /* visibleVolumes */, extraArgs);
+ null /* packagesForUid */, null /* visibleVolumes */,
+ false /* useBlastulaPool */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index c167ea1..8bd75d7 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -44,6 +44,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Set;
+
/**
* <p>
* The contract between the calendar provider and applications. Contains
@@ -217,7 +219,7 @@
* The intent will have its action set to
* {@link CalendarContract#ACTION_VIEW_WORK_CALENDAR_EVENT} and contain extras
* corresponding to the API's arguments. A calendar app intending to support
- * cross profile events viewing should handle this intent, parse the arguments
+ * cross-profile events viewing should handle this intent, parse the arguments
* and show the appropriate UI.
*
* @param context the context.
@@ -767,9 +769,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1758,9 +1761,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar is disabled in Settings, or this uri is
+ * of a managed profile, or cross-profile calendar is disabled in Settings, or this uri is
* queried from a package that is not whitelisted by profile owner of the managed profile
- * via {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * via
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
@@ -1968,10 +1972,10 @@
* projection of the query to this uri that are not contained in the above list.
*
* <p>This uri will return an empty cursor if the calling user is not a parent profile
- * of a managed profile, or cross profile calendar for the managed profile is disabled in
+ * of a managed profile, or cross-profile calendar for the managed profile is disabled in
* Settings, or this uri is queried from a package that is not whitelisted by
* profile owner of the managed profile via
- * {@link DevicePolicyManager#addCrossProfileCalendarPackage(ComponentName, String)}.
+ * {@link DevicePolicyManager#setCrossProfileCalendarPackages(ComponentName, Set)}.
*
* @see DevicePolicyManager#getCrossProfileCalendarPackages(ComponentName)
* @see Settings.Secure#CROSS_PROFILE_CALENDAR_ENABLED
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 158d231..6ccd296 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -54,6 +54,7 @@
/**
* Namespace for all Game Driver features.
+ *
* @hide
*/
@SystemApi
@@ -104,6 +105,24 @@
public static final String NAMESPACE_NOTIFICATION_ASSISTANT = "notification_assistant";
/**
+ * Namespace for attention-based features provided by on-device machine intelligence.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface IntelligenceAttention {
+ String NAMESPACE = "intelligence_attention";
+ /**
+ * If {@code true}, enables the attention check.
+ */
+ String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
+ /**
+ * Settings for performing the attention check.
+ */
+ String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+ }
+
+ /**
* Telephony related properties definitions.
*
* @hide
@@ -149,9 +168,8 @@
* Look up the value of a property for a particular namespace.
*
* @param namespace The namespace containing the property to look up.
- * @param name The name of the property to look up.
+ * @param name The name of the property to look up.
* @return the corresponding value, or null if not present.
- *
* @hide
*/
@SystemApi
@@ -173,14 +191,13 @@
* All properties stored for a particular scope can be reverted to their default values
* by passing the namespace to {@link #resetToDefaults(int, String)}.
*
- * @param namespace The namespace containing the property to create or update.
- * @param name The name of the property to create or update.
- * @param value The value to store for the property.
+ * @param namespace The namespace containing the property to create or update.
+ * @param name The name of the property to create or update.
+ * @param value The value to store for the property.
* @param makeDefault Whether to make the new value the default one.
* @return True if the value was set, false if the storage implementation throws errors.
- * @see #resetToDefaults(int, String).
- *
* @hide
+ * @see #resetToDefaults(int, String).
*/
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -199,9 +216,8 @@
*
* @param resetMode The reset mode to use.
* @param namespace Optionally, the specific namespace which resets will be limited to.
- * @see #setProperty(String, String, String, boolean)
- *
* @hide
+ * @see #setProperty(String, String, String, boolean)
*/
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
@@ -218,12 +234,11 @@
* will replace the old namespace and executor. Remove the listener entirely by calling
* {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
*
- * @param namespace The namespace containing properties to monitor.
- * @param executor The executor which will be used to run callbacks.
+ * @param namespace The namespace containing properties to monitor.
+ * @param executor The executor which will be used to run callbacks.
* @param onPropertyChangedListener The listener to add.
- * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
- *
* @hide
+ * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
*/
@SystemApi
@RequiresPermission(READ_DEVICE_CONFIG)
@@ -255,9 +270,8 @@
* property changes.
*
* @param onPropertyChangedListener The listener to remove.
- * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
- *
* @hide
+ * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
*/
@SystemApi
public static void removeOnPropertyChangedListener(
@@ -358,8 +372,8 @@
* Called when a property has changed.
*
* @param namespace The namespace containing the property which has changed.
- * @param name The name of the property which has changed.
- * @param value The new value of the property which has changed.
+ * @param name The name of the property which has changed.
+ * @param value The new value of the property which has changed.
*/
void onPropertyChanged(String namespace, String name, String value);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 618fbd0..bdeacdf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8436,6 +8436,13 @@
new SettingsValidators.PackageNameListValidator(",");
/**
+ * Controls whether aware is enabled.
+ * @hide
+ */
+ public static final String AWARE_ENABLED = "aware_enabled";
+
+ private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -8559,6 +8566,7 @@
SKIP_GESTURE,
SILENCE_GESTURE,
THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ AWARE_ENABLED,
};
/**
@@ -8731,6 +8739,7 @@
VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
+ VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
}
/**
@@ -9493,6 +9502,14 @@
"location_background_throttle_package_whitelist";
/**
+ * Packages that are whitelisted for ignoring location settings (may retrieve location even
+ * when user location settings are off), for emergency purposes.
+ * @hide
+ */
+ public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST =
+ "location_ignore_settings_package_whitelist";
+
+ /**
* Whether to disable location status callbacks in preparation for deprecation.
* @hide
*/
@@ -12150,6 +12167,14 @@
public static final String GUP_BLACKLIST = "gup_blacklist";
/**
+ * Apps on the whitelist that are allowed to use Game Driver.
+ * The string is a list of application package names, seperated by comma.
+ * i.e. <apk1>,<apk2>,...,<apkN>
+ * @hide
+ */
+ public static final String GAME_DRIVER_WHITELIST = "game_driver_whitelist";
+
+ /**
* Ordered GPU debug layer list for Vulkan
* i.e. <layer1>:<layer2>:...:<layerN>
* @hide
@@ -12194,6 +12219,31 @@
public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
/**
+ * When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+ * saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+ * above this threshold.
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL =
+ "low_power_sticky_auto_disable_level";
+
+ private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
+ new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
+
+ /**
+ * Whether sticky battery saver should be deactivated once the battery level has reached the
+ * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}.
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED =
+ "low_power_sticky_auto_disable_enabled";
+
+ private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
+ new SettingsValidators.DiscreteValueValidator(new String[] {"0", "1"});
+
+ /**
* Battery level [1-100] at which low power mode automatically turns on.
* Pre-Q If 0, it will not automatically turn on. Q and newer it will only automatically
* turn on if the {@link #AUTOMATIC_POWER_SAVER_MODE} setting is also set to
@@ -12205,7 +12255,6 @@
*/
public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
-
private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
new SettingsValidators.InclusiveIntegerRangeValidator(0, 100);
@@ -13196,6 +13245,8 @@
ENCODED_SURROUND_OUTPUT,
ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
LOW_POWER_MODE_TRIGGER_LEVEL,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
BLUETOOTH_ON,
PRIVATE_DNS_MODE,
PRIVATE_DNS_SPECIFIER,
@@ -13234,6 +13285,10 @@
VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR);
+ VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR);
+ VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR);
VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
@@ -13748,6 +13803,19 @@
"user_preferred_sub2","user_preferred_sub3"};
/**
+ * Which subscription is enabled for a physical slot.
+ * @hide
+ */
+ public static final String ENABLED_SUBSCRIPTION_FOR_SLOT = "enabled_subscription_for_slot";
+
+ /**
+ * Whether corresponding logical modem is enabled for a physical slot.
+ * The value 1 - enable, 0 - disable
+ * @hide
+ */
+ public static final String MODEM_STACK_ENABLED_FOR_SLOT = "modem_stack_enabled_for_slot";
+
+ /**
* Whether to enable new contacts aggregator or not.
* The value 1 - enable, 0 - disable
* @hide
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 302e1a6..020de7f 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -116,6 +116,13 @@
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleFinishSession,
ContentCaptureService.this, sessionId));
}
+
+ @Override
+ public void onUserDataRemovalRequest(UserDataRemovalRequest request) {
+ mHandler.sendMessage(
+ obtainMessage(ContentCaptureService::handleOnUserDataRemovalRequest,
+ ContentCaptureService.this, request));
+ }
};
/**
@@ -431,6 +438,10 @@
onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
}
+ private void handleOnUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ onUserDataRemovalRequest(request);
+ }
+
/**
* Checks if the given {@code uid} owns the session associated with the event.
*/
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index a8dd213..d92fb3b 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -19,6 +19,7 @@
import android.os.IBinder;
import android.service.contentcapture.SnapshotData;
import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.os.IResultReceiver;
@@ -36,4 +37,5 @@
in IResultReceiver clientReceiver);
void onSessionFinished(String sessionId);
void onActivitySnapshot(String sessionId, in SnapshotData snapshotData);
+ void onUserDataRemovalRequest(in UserDataRemovalRequest request);
}
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index de532b7..b6788f5 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,8 +15,6 @@
*/
package android.service.notification;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.os.Bundle;
import android.os.Parcel;
@@ -24,10 +22,7 @@
/**
* Ranking updates from the Assistant.
- * @hide
*/
-@SystemApi
-@TestApi
public final class Adjustment implements Parcelable {
private final String mPackage;
private final String mKey;
@@ -39,6 +34,7 @@
* Data type: ArrayList of {@code String}, where each is a representation of a
* {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
* See {@link android.app.Notification.Builder#addPerson(String)}.
+ * @hide
*/
public static final String KEY_PEOPLE = "key_people";
/**
@@ -46,6 +42,7 @@
* users. If a user chooses to snooze a notification until one of these criterion, the
* assistant will be notified via
* {@link NotificationAssistantService#onNotificationSnoozedUntilContext}.
+ * @hide
*/
public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
/**
@@ -112,7 +109,7 @@
mUser = user;
}
- protected Adjustment(Parcel in) {
+ private Adjustment(Parcel in) {
if (in.readInt() == 1) {
mPackage = in.readString();
} else {
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index ad34ab3..e93b158 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -21,8 +21,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.admin.DevicePolicyManager;
@@ -61,11 +59,7 @@
* <p>
* All callbacks are called on the main thread.
* </p>
- *
- * @hide
*/
-@SystemApi
-@TestApi
public abstract class NotificationAssistantService extends NotificationListenerService {
private static final String TAG = "NotificationAssistants";
@@ -109,6 +103,7 @@
*
* @param sbn the notification to snooze
* @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context.
+ * @hide
*/
abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
String snoozeCriterionId);
@@ -250,6 +245,7 @@
* {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
* notification.
* @param key The key of the notification to snooze
+ * @hide
*/
public final void unsnoozeNotification(String key) {
if (!isBound()) return;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0e63cd37..c734b63 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1617,14 +1617,16 @@
}
/**
- * @hide
+ * Returns a list of smart {@link Notification.Action} that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<Notification.Action> getSmartActions() {
return mSmartActions;
}
/**
- * @hide
+ * Returns a list of smart replies that can be added by the
+ * {@link NotificationAssistantService}
*/
public List<CharSequence> getSmartReplies() {
return mSmartReplies;
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index e5f3dfb..814b477 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,8 +16,6 @@
package android.service.notification;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
import android.app.RemoteInput;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,11 +23,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/**
- * @hide
- */
-@TestApi
-@SystemApi
public final class NotificationStats implements Parcelable {
private boolean mSeen;
@@ -105,7 +98,7 @@
public NotificationStats() {
}
- protected NotificationStats(Parcel in) {
+ private NotificationStats(Parcel in) {
mSeen = in.readByte() != 0;
mExpanded = in.readByte() != 0;
mDirectReplied = in.readByte() != 0;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index e105fdf..2789651 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -40,6 +41,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -77,6 +80,33 @@
*/
public static final String SERVICE_META_DATA = "android.voice_interaction";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"VOICE_STATE_"}, value = {
+ VOICE_STATE_NONE,
+ VOICE_STATE_CONDITIONAL_LISTENING,
+ VOICE_STATE_LISTENING,
+ VOICE_STATE_FULFILLING})
+ public @interface VoiceState {
+ }
+
+ /**
+ * Voice assistant inactive.
+ */
+ public static final int VOICE_STATE_NONE = 0;
+ /**
+ * Voice assistant listening, but will only trigger if it hears a request it can fulfill.
+ */
+ public static final int VOICE_STATE_CONDITIONAL_LISTENING = 1;
+ /**
+ * Voice assistant is listening to user speech.
+ */
+ public static final int VOICE_STATE_LISTENING = 2;
+ /**
+ * Voice assistant is fulfilling an action requested by the user.
+ */
+ public static final int VOICE_STATE_FULFILLING = 3;
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override
public void ready() {
@@ -341,6 +371,43 @@
}
}
+ /**
+ * Requests that the voice state UI indicate the given state.
+ *
+ * @param state value indicating whether the assistant is listening, fulfilling, etc.
+ */
+ public final void setVoiceState(@VoiceState int state) {
+ try {
+ mSystemService.setVoiceState(state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Displays the given voice transcription contents.
+ */
+ public final void setTranscription(@NonNull String transcription) {
+ try {
+ mSystemService.setTranscription(transcription);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Hides transcription.
+ *
+ * @param immediate if {@code true}, remove before transcription animation completes.
+ */
+ public final void clearTranscription(boolean immediate) {
+ try {
+ mSystemService.clearTranscription(immediate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("VOICE INTERACTION");
diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
new file mode 100644
index 0000000..d5f0484
--- /dev/null
+++ b/core/java/android/util/LongArrayQueue.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * A lightweight implementation for a queue with long values.
+ * Additionally supports getting an element with a specified position from the head of the queue.
+ * The queue grows in size if needed to accommodate new elements.
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = GrowingArrayUtils.growSize(mSize);
+ final long[] newArray = ArrayUtils.newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 567b279..1d2cf4b 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -24,6 +24,8 @@
import static android.view.RemoteAnimationTargetProto.POSITION;
import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX;
import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_BOUNDS;
+import static android.view.RemoteAnimationTargetProto.START_LEASH;
import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
@@ -57,9 +59,15 @@
*/
public static final int MODE_CLOSING = 1;
+ /**
+ * The app is in the set of resizing apps (eg. mode change) of this transition.
+ */
+ public static final int MODE_CHANGING = 2;
+
@IntDef(prefix = { "MODE_" }, value = {
MODE_OPENING,
- MODE_CLOSING
+ MODE_CLOSING,
+ MODE_CHANGING
})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {}
@@ -83,6 +91,13 @@
public final SurfaceControl leash;
/**
+ * The {@link SurfaceControl} for the starting state of a target if this transition is
+ * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window.
+ */
+ @UnsupportedAppUsage
+ public final SurfaceControl startLeash;
+
+ /**
* Whether the app is translucent and may reveal apps behind.
*/
@UnsupportedAppUsage
@@ -128,6 +143,15 @@
public final Rect sourceContainerBounds;
/**
+ * The starting bounds of the source container in screen space coordinates. This is {@code null}
+ * if the animation target isn't MODE_CHANGING. Since this is the starting bounds, it's size
+ * should be equivalent to the size of the starting thumbnail. Note that sourceContainerBounds
+ * is the end bounds of a change transition.
+ */
+ @UnsupportedAppUsage
+ public final Rect startBounds;
+
+ /**
* The window configuration for the target.
*/
@UnsupportedAppUsage
@@ -141,7 +165,8 @@
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
- Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents) {
+ Rect sourceContainerBounds, WindowConfiguration windowConfig, boolean isNotInRecents,
+ SurfaceControl startLeash, Rect startBounds) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
@@ -153,6 +178,8 @@
this.sourceContainerBounds = new Rect(sourceContainerBounds);
this.windowConfiguration = windowConfig;
this.isNotInRecents = isNotInRecents;
+ this.startLeash = startLeash;
+ this.startBounds = startBounds == null ? null : new Rect(startBounds);
}
public RemoteAnimationTarget(Parcel in) {
@@ -167,6 +194,8 @@
sourceContainerBounds = in.readParcelable(null);
windowConfiguration = in.readParcelable(null);
isNotInRecents = in.readBoolean();
+ startLeash = in.readParcelable(null);
+ startBounds = in.readParcelable(null);
}
@Override
@@ -187,6 +216,8 @@
dest.writeParcelable(sourceContainerBounds, 0 /* flags */);
dest.writeParcelable(windowConfiguration, 0 /* flags */);
dest.writeBoolean(isNotInRecents);
+ dest.writeParcelable(startLeash, 0 /* flags */);
+ dest.writeParcelable(startBounds, 0 /* flags */);
}
public void dump(PrintWriter pw, String prefix) {
@@ -215,6 +246,8 @@
position.writeToProto(proto, POSITION);
sourceContainerBounds.writeToProto(proto, SOURCE_CONTAINER_BOUNDS);
windowConfiguration.writeToProto(proto, WINDOW_CONFIGURATION);
+ startLeash.writeToProto(proto, START_LEASH);
+ startBounds.writeToProto(proto, START_BOUNDS);
proto.end(token);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d0c9f4..483280e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13994,7 +13994,7 @@
if (clickable) {
setPressed(true, x, y);
}
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
return true;
}
}
@@ -14735,7 +14735,7 @@
mHasPerformedLongPress = false;
if (!clickable) {
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
break;
}
@@ -14759,7 +14759,7 @@
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
- checkForLongClick(0, x, y);
+ checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
}
break;
@@ -25434,7 +25434,7 @@
}
}
- private void checkForLongClick(int delayOffset, float x, float y) {
+ private void checkForLongClick(long delay, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
@@ -25444,8 +25444,7 @@
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
- postDelayed(mPendingCheckForLongPress,
- ViewConfiguration.getLongPressTimeout() - delayOffset);
+ postDelayed(mPendingCheckForLongPress, delay);
}
}
@@ -27035,7 +27034,9 @@
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
- checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
+ final long delay =
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
+ checkForLongClick(delay, x, y);
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 45c3651..6326c59 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -255,6 +255,12 @@
int TRANSIT_CRASHING_ACTIVITY_CLOSE = 26;
/**
+ * A task is changing windowing modes
+ * @hide
+ */
+ int TRANSIT_TASK_CHANGE_WINDOWING_MODE = 27;
+
+ /**
* @hide
*/
@IntDef(prefix = { "TRANSIT_" }, value = {
@@ -280,7 +286,8 @@
TRANSIT_KEYGUARD_UNOCCLUDE,
TRANSIT_TRANSLUCENT_ACTIVITY_OPEN,
TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE,
- TRANSIT_CRASHING_ACTIVITY_CLOSE
+ TRANSIT_CRASHING_ACTIVITY_CLOSE,
+ TRANSIT_TASK_CHANGE_WINDOWING_MODE
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionType {}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index c5c1bca..6aafa34 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1166,9 +1166,10 @@
/**
* Notifies that the accessibility button in the system's navigation area has been clicked
*
+ * @param displayId The logical display id.
* @hide
*/
- public void notifyAccessibilityButtonClicked() {
+ public void notifyAccessibilityButtonClicked(int displayId) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1177,7 +1178,7 @@
}
}
try {
- service.notifyAccessibilityButtonClicked();
+ service.notifyAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 38dac94..486b35d 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -63,7 +63,7 @@
IBinder getWindowToken(int windowId, int userId);
- void notifyAccessibilityButtonClicked();
+ void notifyAccessibilityButtonClicked(int displayId);
void notifyAccessibilityButtonVisibilityChanged(boolean available);
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 355b182..1928613 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -22,6 +22,7 @@
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -84,6 +85,7 @@
// Fields below are set by app on Builder
private final @Nullable Bundle mExtras;
private final @Nullable Uri mUri;
+ private final @Nullable String mAction;
// Fields below are set by server when the session starts
private final @Nullable ComponentName mComponentName;
@@ -101,10 +103,12 @@
mHasClientContext = true;
mExtras = clientContext.mExtras;
mUri = clientContext.mUri;
+ mAction = clientContext.mAction;
} else {
mHasClientContext = false;
mExtras = null;
mUri = null;
+ mAction = null;
}
mComponentName = Preconditions.checkNotNull(componentName);
mTaskId = taskId;
@@ -116,13 +120,14 @@
mHasClientContext = true;
mExtras = builder.mExtras;
mUri = builder.mUri;
+ mAction = builder.mAction;
mComponentName = null;
mTaskId = mFlags = mDisplayId = 0;
}
/**
- * Gets the (optional) extras set by the app.
+ * Gets the (optional) extras set by the app (through {@link Builder#setExtras(Bundle)}).
*
* <p>It can be used to provide vendor-specific data that can be modified and examined.
*
@@ -135,7 +140,7 @@
}
/**
- * Gets the (optional) URI set by the app.
+ * Gets the (optional) URI set by the app (through {@link Builder#setUri(Uri)}).
*
* @hide
*/
@@ -146,6 +151,17 @@
}
/**
+ * Gets the (optional) action set by the app (through {@link Builder#setAction(String)}).
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public String getAction() {
+ return mAction;
+ }
+
+ /**
* Gets the id of the {@link TaskInfo task} associated with this context.
*
* @hide
@@ -213,6 +229,7 @@
private Bundle mExtras;
private Uri mUri;
private boolean mDestroyed;
+ private String mAction;
/**
* Sets extra options associated with this context.
@@ -249,17 +266,33 @@
}
/**
+ * Sets an {@link Intent#getAction() intent action} associated with this context.
+ *
+ * @param action intent action
+ *
+ * @return this builder
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ */
+ @NonNull
+ public Builder setAction(@NonNull String action) {
+ mAction = Preconditions.checkNotNull(action);
+ throwIfDestroyed();
+ return this;
+ }
+
+ /**
* Builds the {@link ContentCaptureContext}.
*
* @throws IllegalStateException if {@link #build()} was already called or no call to either
- * {@link #setExtras(Bundle)} or {@link #setUri(Uri)} was made.
+ * {@link #setExtras(Bundle)}, {@link #setAction(String)}, or {@link #setUri(Uri)} was made.
*
* @return the built {@code ContentCaptureContext}
*/
public ContentCaptureContext build() {
throwIfDestroyed();
- Preconditions.checkState(mExtras != null || mUri != null, "Must call setUri() "
- + "or setExtras() before calling build()");
+ Preconditions.checkState(mExtras != null || mUri != null || mAction != null,
+ "Must call setUri() or setExtras() or setUri() before calling build()");
mDestroyed = true;
return new ContentCaptureContext(this);
}
@@ -291,6 +324,10 @@
// NOTE: cannot dump because it could contain PII
pw.print(", hasUri");
}
+ if (mAction != null) {
+ // NOTE: cannot dump because it could contain PII
+ pw.print(", hasAction");
+ }
}
@Override
@@ -311,6 +348,10 @@
// NOTE: cannot print because it could contain PII
builder.append(", hasUri");
}
+ if (mAction != null) {
+ // NOTE: cannot print because it could contain PII
+ builder.append(", hasAction");
+ }
return builder.append(']').toString();
}
@@ -324,6 +365,7 @@
parcel.writeInt(mHasClientContext ? 1 : 0);
if (mHasClientContext) {
parcel.writeParcelable(mUri, flags);
+ parcel.writeString(mAction);
parcel.writeBundle(mExtras);
}
parcel.writeParcelable(mComponentName, flags);
@@ -343,12 +385,14 @@
final ContentCaptureContext clientContext;
if (hasClientContext) {
+ // Must reconstruct the client context using the Builder API
final Builder builder = new Builder();
final Uri uri = parcel.readParcelable(null);
+ final String action = parcel.readString();
final Bundle extras = parcel.readBundle();
if (uri != null) builder.setUri(uri);
+ if (action != null) builder.setAction(action);
if (extras != null) builder.setExtras(extras);
- // Must reconstruct the client context using the Builder API
clientContext = new ContentCaptureContext(builder);
} else {
clientContext = null;
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 3474e23..b9017b3 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -189,13 +189,19 @@
}
/**
- * Called by the ap to request the Content Capture service to remove user-data associated with
+ * Called by the app to request the Content Capture service to remove user-data associated with
* some context.
*
* @param request object specifying what user data should be removed.
*/
public void removeUserData(@NonNull UserDataRemovalRequest request) {
- //TODO(b/111276913): implement
+ Preconditions.checkNotNull(request);
+
+ try {
+ mService.removeUserData(mContext.getUserId(), request);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/** @hide */
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index be9c00f..51aea16 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.UserDataRemovalRequest;
import android.os.IBinder;
import com.android.internal.os.IResultReceiver;
@@ -56,4 +57,9 @@
* provided {@code Bundle} with key "{@code EXTRA}".
*/
void getReceiverServiceComponentName(int userId, in IResultReceiver result);
+
+ /**
+ * Requests the removal of user data for the provided {@code userId}.
+ */
+ void removeUserData(int userId, in UserDataRemovalRequest request);
}
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
new file mode 100644
index 0000000..fbe47e0
--- /dev/null
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.view.contentcapture;
+
+parcelable UserDataRemovalRequest;
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
index 0261b70..8ee63ef 100644
--- a/core/java/android/view/contentcapture/UserDataRemovalRequest.java
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -17,10 +17,15 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.app.ActivityThread;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.IntArray;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
import java.util.List;
/**
@@ -29,8 +34,35 @@
*/
public final class UserDataRemovalRequest implements Parcelable {
- private UserDataRemovalRequest(Builder builder) {
- // TODO(b/111276913): implement
+ private final String mPackageName;
+
+ private final boolean mForEverything;
+ private ArrayList<UriRequest> mUriRequests;
+
+ private UserDataRemovalRequest(@NonNull Builder builder) {
+ mPackageName = ActivityThread.currentActivityThread().getApplication().getPackageName();
+ mForEverything = builder.mForEverything;
+ if (builder.mUris != null) {
+ final int size = builder.mUris.size();
+ mUriRequests = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUriRequests.add(new UriRequest(builder.mUris.get(i),
+ builder.mRecursive.get(i) == 1));
+ }
+ }
+ }
+
+ private UserDataRemovalRequest(@NonNull Parcel parcel) {
+ mPackageName = parcel.readString();
+ mForEverything = parcel.readBoolean();
+ if (!mForEverything) {
+ final int size = parcel.readInt();
+ mUriRequests = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ mUriRequests.add(new UriRequest((Uri) parcel.readValue(null),
+ parcel.readBoolean()));
+ }
+ }
}
/**
@@ -40,9 +72,7 @@
@SystemApi
@NonNull
public String getPackageName() {
- // TODO(b/111276913): implement
- // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
- return null;
+ return mPackageName;
}
/**
@@ -52,8 +82,7 @@
*/
@SystemApi
public boolean isForEverything() {
- // TODO(b/111276913): implement
- return false;
+ return mForEverything;
}
/**
@@ -64,8 +93,7 @@
@SystemApi
@NonNull
public List<UriRequest> getUriRequests() {
- // TODO(b/111276913): implement
- return null;
+ return mUriRequests;
}
/**
@@ -73,6 +101,12 @@
*/
public static final class Builder {
+ private boolean mForEverything;
+ private ArrayList<Uri> mUris;
+ private IntArray mRecursive;
+
+ private boolean mDestroyed;
+
/**
* Requests servive to remove all user data associated with the app's package.
*
@@ -80,7 +114,12 @@
*/
@NonNull
public Builder forEverything() {
- // TODO(b/111276913): implement
+ throwIfDestroyed();
+ if (mUris != null) {
+ throw new IllegalStateException("Already added Uris");
+ }
+
+ mForEverything = true;
return this;
}
@@ -94,7 +133,19 @@
* @return this builder
*/
public Builder addUri(@NonNull Uri uri, boolean recursive) {
- // TODO(b/111276913): implement
+ throwIfDestroyed();
+ if (mForEverything) {
+ throw new IllegalStateException("Already is for everything");
+ }
+ Preconditions.checkNotNull(uri);
+
+ if (mUris == null) {
+ mUris = new ArrayList<>();
+ mRecursive = new IntArray();
+ }
+
+ mUris.add(uri);
+ mRecursive.add(recursive ? 1 : 0);
return this;
}
@@ -103,8 +154,16 @@
*/
@NonNull
public UserDataRemovalRequest build() {
- // TODO(b/111276913): implement / unit test / check built / document exceptions
- return null;
+ throwIfDestroyed();
+
+ Preconditions.checkState(mForEverything || mUris != null);
+
+ mDestroyed = true;
+ return new UserDataRemovalRequest(this);
+ }
+
+ private void throwIfDestroyed() {
+ Preconditions.checkState(!mDestroyed, "Already destroyed!");
}
}
@@ -115,7 +174,17 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
- // TODO(b/111276913): implement
+ parcel.writeString(mPackageName);
+ parcel.writeBoolean(mForEverything);
+ if (!mForEverything) {
+ final int size = mUriRequests.size();
+ parcel.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ final UriRequest request = mUriRequests.get(i);
+ parcel.writeValue(request.getUri());
+ parcel.writeBoolean(request.isRecursive());
+ }
+ }
}
public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
@@ -123,8 +192,7 @@
@Override
public UserDataRemovalRequest createFromParcel(Parcel parcel) {
- // TODO(b/111276913): implement
- return null;
+ return new UserDataRemovalRequest(parcel);
}
@Override
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 57ed2f8..0cb1800 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2490,7 +2490,14 @@
* @param subtype A new input method subtype to switch.
* @return true if the current subtype was successfully switched. When the specified subtype is
* null, this method returns false.
+ * @deprecated If the calling process is an IME, use
+ * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
+ * does not require any permission as long as the caller is the current IME.
+ * If the calling process is some privileged app that already has
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+ * directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
*/
+ @Deprecated
@RequiresPermission(WRITE_SECURE_SETTINGS)
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
if (Process.myUid() == Process.SYSTEM_UID) {
diff --git a/core/java/android/view/inspector/InspectableProperty.java b/core/java/android/view/inspector/InspectableProperty.java
index 355ff1d..f859521 100644
--- a/core/java/android/view/inspector/InspectableProperty.java
+++ b/core/java/android/view/inspector/InspectableProperty.java
@@ -106,6 +106,7 @@
/**
* One entry in an enumeration packed into a primitive {int}.
*
+ * @see IntEnumMapping
* @hide
*/
@Target({TYPE})
diff --git a/core/java/android/view/inspector/IntEnumMapping.java b/core/java/android/view/inspector/IntEnumMapping.java
new file mode 100644
index 0000000..147bb46
--- /dev/null
+++ b/core/java/android/view/inspector/IntEnumMapping.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2019 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.view.inspector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.Objects;
+
+/**
+ * Maps the values of an {@code int} property to strings for properties that encode an enumeration.
+ *
+ * An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
+ * for flag values packed into primitive {@code int} properties.
+ *
+ * This class is an immutable wrapper for {@link SparseArray}, and must be constructed by a
+ * {@link Builder}.
+ *
+ * @see PropertyMapper#mapIntEnum(String, int, IntEnumMapping)
+ */
+public final class IntEnumMapping {
+ private final SparseArray<String> mValues;
+
+ /**
+ * Get the name for the given property value
+ *
+ * @param value The value of the property
+ * @return The name of the value in the enumeration, or null if no value is defined
+ */
+ @Nullable
+ public String get(int value) {
+ return mValues.get(value);
+ }
+
+ /**
+ * Create a new instance from a builder.
+ *
+ * This constructor is private, use {@link Builder#build()} instead.
+ *
+ * @param builder A builder to create from
+ */
+ private IntEnumMapping(Builder builder) {
+ mValues = builder.mValues.clone();
+ }
+
+ /**
+ * A builder for {@link IntEnumMapping}.
+ */
+ public static final class Builder {
+ @NonNull
+ private SparseArray<String> mValues;
+ private boolean mMustCloneValues = false;
+
+ public Builder() {
+ mValues = new SparseArray<>();
+ }
+
+ /**
+ * Add a new enumerated value.
+ *
+ * @param name The string name of the enumeration value
+ * @param value The {@code int} value of the enumeration value
+ * @return This builder
+ */
+ @NonNull
+ public Builder addValue(@NonNull String name, int value) {
+ // Save an allocation, only re-clone if the builder is used again after building
+ if (mMustCloneValues) {
+ mValues = mValues.clone();
+ }
+
+ mValues.put(value, Objects.requireNonNull(name));
+ return this;
+ }
+
+ /**
+ * Build a new {@link IntEnumMapping} from this builder.
+ *
+ * @return A new mapping
+ */
+ @NonNull
+ public IntEnumMapping build() {
+ mMustCloneValues = true;
+ return new IntEnumMapping(this);
+ }
+ }
+}
diff --git a/core/java/android/view/inspector/IntFlagMapping.java b/core/java/android/view/inspector/IntFlagMapping.java
index 8f7dfd5..2409081 100644
--- a/core/java/android/view/inspector/IntFlagMapping.java
+++ b/core/java/android/view/inspector/IntFlagMapping.java
@@ -21,10 +21,11 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
/**
- * Maps the values of an {@code int} property to arrays of string for properties that encode flags.
+ * Maps the values of an {@code int} property to sets of string for properties that encode flags.
*
* An {@link InspectionCompanion} may provide an instance of this class to a {@link PropertyMapper}
* for flag values packed into primitive {@code int} properties.
@@ -45,7 +46,7 @@
* Get an array of the names of enabled flags for a given property value.
*
* @param value The value of the property
- * @return The names of the enabled flags
+ * @return The names of the enabled flags, empty if no flags enabled
*/
@NonNull
public Set<String> get(int value) {
@@ -136,7 +137,7 @@
private final int mMask;
private Flag(@NonNull String name, int target, int mask) {
- mName = name;
+ mName = Objects.requireNonNull(name);
mTarget = target;
mMask = mask;
}
diff --git a/core/java/android/view/inspector/PropertyMapper.java b/core/java/android/view/inspector/PropertyMapper.java
index e20582b..00b18d1 100644
--- a/core/java/android/view/inspector/PropertyMapper.java
+++ b/core/java/android/view/inspector/PropertyMapper.java
@@ -18,7 +18,6 @@
import android.annotation.AttrRes;
import android.annotation.NonNull;
-import android.util.SparseArray;
/**
* An interface for mapping the string names of inspectable properties to integer identifiers.
@@ -155,14 +154,14 @@
int mapIntEnum(
@NonNull String name,
@AttrRes int attributeId,
- @NonNull SparseArray<String> mapping);
+ @NonNull IntEnumMapping mapping);
/**
* Map a string name to an integer ID for a flag set packed into an int property.
*
* @param name The name of the property
* @param attributeId If the property is from an XML attribute, the resource ID of the property
- * @param mapping A mapping from int to an array of strings
+ * @param mapping A mapping from int to a set of strings
* @return An integer ID for the property
* @throws PropertyConflictException If the property name is already mapped as another type.
*/
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9ab963e..a5b7c62 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -78,6 +78,9 @@
*/
public final class TextClassifierImpl implements TextClassifier {
+ /** @hide */
+ public static final String ACTIONS_INTENTS = "actions-intents";
+
private static final String LOG_TAG = DEFAULT_LOG_TAG;
private static final boolean DEBUG = false;
@@ -567,6 +570,7 @@
// TODO: Make this configurable.
final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
boolean isPrimaryAction = true;
+ final ArrayList<Intent> sourceIntents = new ArrayList<>();
for (LabeledIntent labeledIntent : IntentFactory.create(
mContext, classifiedText, isForeignText(classifiedText, foreignTextThreshold),
referenceTime, highestScoringResult)) {
@@ -586,9 +590,15 @@
isPrimaryAction = false;
}
builder.addAction(action);
+ sourceIntents.add(labeledIntent.getIntent());
}
- return builder.setId(createId(text, start, end)).build();
+ final Bundle extras = new Bundle();
+ extras.putParcelableArrayList(ACTIONS_INTENTS, sourceIntents);
+
+ return builder.setId(createId(text, start, end))
+ .setExtras(extras)
+ .build();
}
private boolean isForeignText(String text, float threshold) {
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 5088cca..b85488f 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -151,4 +151,19 @@
*/
void getActiveServiceSupportedActions(in List<String> voiceActions,
in IVoiceActionCheckCallback callback);
+
+ /**
+ * Sets the transcribed voice to the given string.
+ */
+ void setTranscription(String transcription);
+
+ /**
+ * Indicates that the transcription session is finished.
+ */
+ void clearTranscription(boolean immediate);
+
+ /**
+ * Sets the voice state indication based upon the given value.
+ */
+ void setVoiceState(int state);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
index 87749d2..674ad5b 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
@@ -26,4 +26,20 @@
* Called when a voice session is hidden.
*/
void onVoiceSessionHidden();
+
+ /**
+ * Called when voice assistant transcription has been updated to the given string.
+ */
+ void onTranscriptionUpdate(in String transcription);
+
+ /**
+ * Called when voice transcription is completed.
+ */
+ void onTranscriptionComplete(in boolean immediate);
+
+ /**
+ * Called when the voice assistant's state has changed. Values are from
+ * VoiceInteractionService's VOICE_STATE* constants.
+ */
+ void onVoiceStateChange(in int state);
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index fd03b3f..da8605e 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -28,6 +28,7 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.Network;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,6 +105,7 @@
public boolean allowIPv4;
public boolean allowIPv6;
public Network[] underlyingNetworks;
+ public ProxyInfo proxyInfo;
public void updateAllowedFamilies(InetAddress address) {
if (address instanceof Inet4Address) {
@@ -164,6 +166,7 @@
out.writeInt(allowIPv4 ? 1 : 0);
out.writeInt(allowIPv6 ? 1 : 0);
out.writeTypedArray(underlyingNetworks, flags);
+ out.writeParcelable(proxyInfo, flags);
}
public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -189,6 +192,7 @@
config.allowIPv4 = in.readInt() != 0;
config.allowIPv6 = in.readInt() != 0;
config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
+ config.proxyInfo = in.readParcelable(null);
return config;
}
@@ -220,6 +224,7 @@
.append(", allowIPv4=").append(allowIPv4)
.append(", allowIPv6=").append(allowIPv6)
.append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks))
+ .append(", proxyInfo=").append(proxyInfo.toString())
.append("}")
.toString();
}
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index a676dac..b1a41287 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -32,11 +32,11 @@
@Override
public String toString() {
- return "VpnInfo{" +
- "ownerUid=" + ownerUid +
- ", vpnIface='" + vpnIface + '\'' +
- ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' +
- '}';
+ return "VpnInfo{"
+ + "ownerUid=" + ownerUid
+ + ", vpnIface='" + vpnIface + '\''
+ + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
+ + '}';
}
@Override
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index f609f2f..069413f 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -117,7 +117,13 @@
/** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;
- /** If the blastula pool should be created and used to start applications */
+ /**
+ * If the blastula pool should be created and used to start applications.
+ *
+ * Setting this value to false will disable the creation, maintenance, and use of the blastula
+ * pool. When the blastula pool is disabled the application lifecycle will be identical to
+ * previous versions of Android.
+ */
public static final boolean BLASTULA_POOL_ENABLED = false;
/**
@@ -187,6 +193,11 @@
// TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);
+ /**
+ * @hide for internal use only
+ */
+ public static final int SOCKET_BUFFER_SIZE = 256;
+
private static LocalServerSocket sBlastulaPoolSocket = null;
/** a prototype instance for a future List.toArray() */
@@ -234,14 +245,14 @@
public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- String packageName, String[] packagesForUid, String[] visibleVolIds) {
+ String packageName, String[] packagesForUID, String[] visibleVolIDs) {
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
- packagesForUid, visibleVolIds);
+ packagesForUID, visibleVolIDs);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true, runtimeFlags);
@@ -256,7 +267,7 @@
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
+ String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);
/**
* Specialize a Blastula instance. The current VM must have been started
@@ -281,12 +292,12 @@
*/
public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir,
- String packageName, String[] packagesForUid, String[] visibleVolIds) {
+ boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
+ String[] packagesForUID, String[] visibleVolIDs) {
nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
niceName, startChildZygote, instructionSet, appDataDir,
- packageName, packagesForUid, visibleVolIds);
+ packageName, packagesForUID, visibleVolIDs);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,7 +317,7 @@
private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
- String[] packagesForUid, String[] visibleVolIds);
+ String[] packagesForUID, String[] visibleVolIDs);
/**
* Called to do any initialization before starting an application.
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 78ecee1..ffbe8eb 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -242,7 +242,7 @@
fdsToClose[0] = fd.getInt$();
}
- fd = zygoteServer.getServerSocketFileDescriptor();
+ fd = zygoteServer.getZygoteSocketFileDescriptor();
if (fd != null) {
fdsToClose[1] = fd.getInt$();
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2f00c07..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -269,7 +269,7 @@
try {
BufferedReader br =
- new BufferedReader(new InputStreamReader(is), 256);
+ new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0;
String line;
@@ -750,7 +750,7 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
- final Runnable caller;
+ Runnable caller;
try {
// Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -786,7 +786,17 @@
throw new RuntimeException("No ABI list supplied.");
}
- zygoteServer.registerServerSocketFromEnv(socketName);
+ // TODO (chriswailes): Wrap these three calls in a helper function?
+ final String blastulaSocketName =
+ socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)
+ ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME
+ : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME;
+
+ zygoteServer.createZygoteSocket(socketName);
+ Zygote.createBlastulaSocket(blastulaSocketName);
+
+ Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME));
+
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
@@ -829,11 +839,18 @@
}
}
- Log.i(TAG, "Accepting command socket connections");
+ // If the return value is null then this is the zygote process
+ // returning to the normal control flow. If it returns a Runnable
+ // object then this is a blastula that has finished specializing.
+ caller = Zygote.initBlastulaPool();
- // The select loop returns early in the child process after a fork and
- // loops forever in the zygote.
- caller = zygoteServer.runSelectLoop(abiList);
+ if (caller == null) {
+ Log.i(TAG, "Accepting command socket connections");
+
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
+ }
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
throw ex;
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index c1bfde1..a78c095 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -26,6 +26,8 @@
import android.util.Log;
import android.util.Slog;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.ArrayList;
@@ -40,18 +42,17 @@
* client protocol.
*/
class ZygoteServer {
+ // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
public static final String TAG = "ZygoteServer";
- private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
-
/**
* Listening socket that accepts new server connections.
*/
- private LocalServerSocket mServerSocket;
+ private LocalServerSocket mZygoteSocket;
/**
- * Whether or not mServerSocket's underlying FD should be closed directly.
- * If mServerSocket is created with an existing FD, closing the socket does
+ * Whether or not mZygoteSocket's underlying FD should be closed directly.
+ * If mZygoteSocket is created with an existing FD, closing the socket does
* not close the FD and it must be closed explicitly. If the socket is created
* with a name instead, then closing the socket will close the underlying FD
* and it should not be double-closed.
@@ -70,31 +71,17 @@
}
/**
- * Registers a server socket for zygote command connections. This locates the server socket
- * file descriptor through an ANDROID_SOCKET_ environment variable.
+ * Creates a managed object representing the Zygote socket that has already
+ * been initialized and bound by init.
+ *
+ * TODO (chriswailes): Move the name selection logic into this function.
*
* @throws RuntimeException when open fails
*/
- void registerServerSocketFromEnv(String socketName) {
- if (mServerSocket == null) {
- int fileDesc;
- final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
- try {
- String env = System.getenv(fullSocketName);
- fileDesc = Integer.parseInt(env);
- } catch (RuntimeException ex) {
- throw new RuntimeException(fullSocketName + " unset or invalid", ex);
- }
-
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(fileDesc);
- mServerSocket = new LocalServerSocket(fd);
- mCloseSocketFd = true;
- } catch (IOException ex) {
- throw new RuntimeException(
- "Error binding to local socket '" + fileDesc + "'", ex);
- }
+ void createZygoteSocket(String socketName) {
+ if (mZygoteSocket == null) {
+ mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName);
+ mCloseSocketFd = true;
}
}
@@ -103,9 +90,9 @@
* at the specified name in the abstract socket namespace.
*/
void registerServerSocketAtAbstractName(String socketName) {
- if (mServerSocket == null) {
+ if (mZygoteSocket == null) {
try {
- mServerSocket = new LocalServerSocket(socketName);
+ mZygoteSocket = new LocalServerSocket(socketName);
mCloseSocketFd = false;
} catch (IOException ex) {
throw new RuntimeException(
@@ -120,7 +107,7 @@
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
- return createNewConnection(mServerSocket.accept(), abiList);
+ return createNewConnection(mZygoteSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
@@ -138,9 +125,9 @@
*/
void closeServerSocket() {
try {
- if (mServerSocket != null) {
- FileDescriptor fd = mServerSocket.getFileDescriptor();
- mServerSocket.close();
+ if (mZygoteSocket != null) {
+ FileDescriptor fd = mZygoteSocket.getFileDescriptor();
+ mZygoteSocket.close();
if (fd != null && mCloseSocketFd) {
Os.close(fd);
}
@@ -151,7 +138,7 @@
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
- mServerSocket = null;
+ mZygoteSocket = null;
}
/**
@@ -160,8 +147,8 @@
* closure after a child process is forked off.
*/
- FileDescriptor getServerSocketFileDescriptor() {
- return mServerSocket.getFileDescriptor();
+ FileDescriptor getZygoteSocketFileDescriptor() {
+ return mZygoteSocket.getFileDescriptor();
}
/**
@@ -170,36 +157,67 @@
* worth at a time.
*/
Runnable runSelectLoop(String abiList) {
- ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+ ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
- fds.add(mServerSocket.getFileDescriptor());
+ socketFDs.add(mZygoteSocket.getFileDescriptor());
peers.add(null);
while (true) {
- StructPollfd[] pollFds = new StructPollfd[fds.size()];
- for (int i = 0; i < pollFds.length; ++i) {
- pollFds[i] = new StructPollfd();
- pollFds[i].fd = fds.get(i);
- pollFds[i].events = (short) POLLIN;
+ int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
+
+ // Space for all of the socket FDs, the Blastula Pool Event FD, and
+ // all of the open blastula read pipe FDs.
+ StructPollfd[] pollFDs =
+ new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
+
+ int pollIndex = 0;
+ for (FileDescriptor socketFD : socketFDs) {
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = socketFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
}
+
+ final int blastulaPoolEventFDIndex = pollIndex;
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+
+ for (int blastulaPipeFD : blastulaPipeFDs) {
+ FileDescriptor managedFd = new FileDescriptor();
+ managedFd.setInt$(blastulaPipeFD);
+
+ pollFDs[pollIndex] = new StructPollfd();
+ pollFDs[pollIndex].fd = managedFd;
+ pollFDs[pollIndex].events = (short) POLLIN;
+ ++pollIndex;
+ }
+
try {
- Os.poll(pollFds, -1);
+ Os.poll(pollFDs, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
- for (int i = pollFds.length - 1; i >= 0; --i) {
- if ((pollFds[i].revents & POLLIN) == 0) {
+
+ while (--pollIndex >= 0) {
+ if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
continue;
}
- if (i == 0) {
+ if (pollIndex == 0) {
+ // Zygote server socket
+
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
- fds.add(newPeer.getFileDescriptor());
- } else {
+ socketFDs.add(newPeer.getFileDescriptor());
+
+ } else if (pollIndex < blastulaPoolEventFDIndex) {
+ // Session socket accepted from the Zygote server socket
+
try {
- ZygoteConnection connection = peers.get(i);
+ ZygoteConnection connection = peers.get(pollIndex);
final Runnable command = connection.processOneCommand(this);
if (mIsForkChild) {
@@ -217,12 +235,12 @@
}
// We don't know whether the remote side of the socket was closed or
- // not until we attempt to read from it from processOneCommand. This shows up as
- // a regular POLLIN event in our regular processing loop.
+ // not until we attempt to read from it from processOneCommand. This
+ // shows up as a regular POLLIN event in our regular processing loop.
if (connection.isClosedByPeer()) {
connection.closeSocket();
- peers.remove(i);
- fds.remove(i);
+ peers.remove(pollIndex);
+ socketFDs.remove(pollIndex);
}
}
} catch (Exception e) {
@@ -234,13 +252,13 @@
Slog.e(TAG, "Exception executing zygote command: ", e);
- // Make sure the socket is closed so that the other end knows immediately
- // that something has gone wrong and doesn't time out waiting for a
- // response.
- ZygoteConnection conn = peers.remove(i);
+ // Make sure the socket is closed so that the other end knows
+ // immediately that something has gone wrong and doesn't time out
+ // waiting for a response.
+ ZygoteConnection conn = peers.remove(pollIndex);
conn.closeSocket();
- fds.remove(i);
+ socketFDs.remove(pollIndex);
} else {
// We're in the child so any exception caught here has happened post
// fork and before we execute ActivityThread.main (or any other main()
@@ -254,6 +272,55 @@
// is returned.
mIsForkChild = false;
}
+ } else {
+ // Either the blastula pool event FD or a blastula reporting pipe.
+
+ // If this is the event FD the payload will be the number of blastulas removed.
+ // If this is a reporting pipe FD the payload will be the PID of the blastula
+ // that was just specialized.
+ long messagePayload = -1;
+
+ try {
+ byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES];
+ int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
+
+ if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) {
+ DataInputStream inputStream =
+ new DataInputStream(new ByteArrayInputStream(buffer));
+
+ messagePayload = inputStream.readLong();
+ } else {
+ Log.e(TAG, "Incomplete read from blastula management FD of size "
+ + readBytes);
+ continue;
+ }
+ } catch (Exception ex) {
+ if (pollIndex == blastulaPoolEventFDIndex) {
+ Log.e(TAG, "Failed to read from blastula pool event FD: "
+ + ex.getMessage());
+ } else {
+ Log.e(TAG, "Failed to read from blastula reporting pipe: "
+ + ex.getMessage());
+ }
+
+ continue;
+ }
+
+ if (pollIndex > blastulaPoolEventFDIndex) {
+ Zygote.removeBlastulaTableEntry((int) messagePayload);
+ }
+
+ int[] sessionSocketRawFDs =
+ socketFDs.subList(1, socketFDs.size())
+ .stream()
+ .mapToInt(fd -> fd.getInt$())
+ .toArray();
+
+ final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs);
+
+ if (command != null) {
+ return command;
+ }
}
}
}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 841e5b6..3537465 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -140,6 +140,10 @@
// without throttling, as read from the configuration files.
final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
+ // These are the packages that are white-listed to be able to retrieve location even when user
+ // location settings are off, for emergency purposes, as read from the configuration files.
+ final ArraySet<String> mAllowIgnoreLocationSettings = new ArraySet<>();
+
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
@@ -255,6 +259,10 @@
return mAllowUnthrottledLocation;
}
+ public ArraySet<String> getAllowIgnoreLocationSettings() {
+ return mAllowIgnoreLocationSettings;
+ }
+
public ArraySet<String> getLinkedApps() {
return mLinkedApps;
}
@@ -682,6 +690,20 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "allow-ignore-location-settings": {
+ if (allowAll) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ mAllowIgnoreLocationSettings.add(pkgname);
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "allow-implicit-broadcast": {
if (allowAll) {
String action = parser.getAttributeValue(null, "action");
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 18d9b5a..f458299 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -152,6 +152,8 @@
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
+extern int register_android_view_InputApplicationHandle(JNIEnv* env);
+extern int register_android_view_InputWindowHandle(JNIEnv* env);
extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
@@ -1370,6 +1372,8 @@
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_DisplayListCanvas),
+ REG_JNI(register_android_view_InputApplicationHandle),
+ REG_JNI(register_android_view_InputWindowHandle),
REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 10005dd..1ab5843 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -93,7 +93,7 @@
// --- Global functions ---
-sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
JNIEnv* env, jobject inputApplicationHandleObj) {
if (!inputApplicationHandleObj) {
return NULL;
@@ -108,7 +108,7 @@
} else {
jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj);
handle = new NativeInputApplicationHandle(objWeak);
- handle->incStrong((void*)android_server_InputApplicationHandle_getHandle);
+ handle->incStrong((void*)android_view_InputApplicationHandle_getHandle);
env->SetLongField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr,
reinterpret_cast<jlong>(handle));
}
@@ -118,7 +118,7 @@
// --- JNI ---
-static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
AutoMutex _l(gHandleMutex);
jlong ptr = env->GetLongField(obj, gInputApplicationHandleClassInfo.ptr);
@@ -126,7 +126,7 @@
env->SetLongField(obj, gInputApplicationHandleClassInfo.ptr, 0);
NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
- handle->decStrong((void*)android_server_InputApplicationHandle_getHandle);
+ handle->decStrong((void*)android_view_InputApplicationHandle_getHandle);
}
}
@@ -134,7 +134,7 @@
static const JNINativeMethod gInputApplicationHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
- (void*) android_server_InputApplicationHandle_nativeDispose },
+ (void*) android_view_InputApplicationHandle_nativeDispose },
};
#define FIND_CLASS(var, className) \
@@ -145,7 +145,7 @@
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-int register_android_server_InputApplicationHandle(JNIEnv* env) {
+int register_android_view_InputApplicationHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/view/InputApplicationHandle",
gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.h b/core/jni/android_hardware_input_InputApplicationHandle.h
index 7115611..5abeab4 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.h
+++ b/core/jni/android_hardware_input_InputApplicationHandle.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
-#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
+#define _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
#include <string>
@@ -40,9 +40,9 @@
};
-extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+extern sp<InputApplicationHandle> android_view_InputApplicationHandle_getHandle(
JNIEnv* env, jobject inputApplicationHandleObj);
} // namespace android
-#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_APPLICATION_HANDLE_H
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 76920f5..c0e45b1 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -159,7 +159,7 @@
gInputWindowHandleClassInfo.inputApplicationHandle);
if (inputApplicationHandleObj) {
sp<InputApplicationHandle> inputApplicationHandle =
- android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
+ android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
if (inputApplicationHandle != nullptr) {
inputApplicationHandle->updateInfo();
mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
@@ -174,7 +174,7 @@
// --- Global functions ---
-sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj) {
if (!inputWindowHandleObj) {
return NULL;
@@ -189,7 +189,7 @@
} else {
jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
handle = new NativeInputWindowHandle(objWeak);
- handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
+ handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
reinterpret_cast<jlong>(handle));
}
@@ -199,7 +199,7 @@
// --- JNI ---
-static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
+static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
AutoMutex _l(gHandleMutex);
jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
@@ -207,7 +207,7 @@
env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- handle->decStrong((void*)android_server_InputWindowHandle_getHandle);
+ handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
}
}
@@ -215,7 +215,7 @@
static const JNINativeMethod gInputWindowHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
- (void*) android_server_InputWindowHandle_nativeDispose },
+ (void*) android_view_InputWindowHandle_nativeDispose },
};
#define FIND_CLASS(var, className) \
@@ -226,7 +226,7 @@
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
-int register_android_server_InputWindowHandle(JNIEnv* env) {
+int register_android_view_InputWindowHandle(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/view/InputWindowHandle",
gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
(void) res; // Faked use when LOG_NDEBUG.
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 54b89f5..de5bd6e 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
-#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#ifndef _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
+#define _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
#include <input/InputWindow.h>
@@ -38,9 +38,9 @@
};
-extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj);
} // namespace android
-#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#endif // _ANDROID_VIEW_INPUT_WINDOW_HANDLE_H
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0453195..69877c7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -360,7 +360,7 @@
jlong nativeObject, jobject inputWindow) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- sp<NativeInputWindowHandle> handle = android_server_InputWindowHandle_getHandle(
+ sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
env, inputWindow);
handle->updateInfo();
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2e7184b..8681d4b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1472,7 +1472,7 @@
fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
-// fds_to_close.push_back(gBlastulaPoolSocketFD);
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
if (gBlastulaPoolEventFD != -1) {
fds_to_close.push_back(gBlastulaPoolEventFD);
@@ -1498,7 +1498,7 @@
std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
fds_to_ignore(fds_to_close);
-// fds_to_close.push_back(gBlastulaPoolSocketFD);
+ fds_to_close.push_back(gBlastulaPoolSocketFD);
if (gBlastulaPoolEventFD != -1) {
fds_to_close.push_back(gBlastulaPoolEventFD);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 53dde80..4e48663 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -72,6 +72,7 @@
return true;
}
+ // Framework jars are allowed.
static const char* kFrameworksPrefix = "/system/framework/";
static const char* kJarSuffix = ".jar";
if (android::base::StartsWith(path, kFrameworksPrefix)
@@ -79,6 +80,13 @@
return true;
}
+ // Jars from the runtime apex are allowed.
+ static const char* kRuntimeApexPrefix = "/apex/com.android.runtime/javalib/";
+ if (android::base::StartsWith(path, kRuntimeApexPrefix)
+ && android::base::EndsWith(path, kJarSuffix)) {
+ return true;
+ }
+
// Whitelist files needed for Runtime Resource Overlay, like these:
// /system/vendor/overlay/framework-res.apk
// /system/vendor/overlay-subdir/pg/framework-res.apk
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6cdba33..eb716ac 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2183,4 +2183,10 @@
// OPEN: Settings > Display > Adaptive sleep
// OS: Q
SETTINGS_ADAPTIVE_SLEEP = 1628;
+
+ // OPEN: Settings > System > Aware
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ DIALOG_AWARE_DISABLE = 1633;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 712a8c0..d79eb94 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -449,6 +449,8 @@
optional SettingProto gup_dev_opt_out_apps = 10;
// GUP - List of Apps that are forbidden to use Game Update Package
optional SettingProto gup_blacklist = 11;
+ // List of Apps that are allowed to use Game Driver package.
+ optional SettingProto game_driver_whitelist = 12;
}
optional Gpu gpu = 59;
@@ -535,6 +537,16 @@
// Whether automatic battery saver mode is controlled via percentage,
// {@link #DYNAMIC_POWER_SAVINGS_ENABLED} or disabled.
optional SettingProto automatic_power_saver_mode = 4 [ (android.privacy).dest = DEST_AUTOMATIC];
+ // If 1, battery saver (low_power_mode) will be re-activated after the device is
+ // unplugged from a charger or rebooted.
+ optional SettingProto sticky_enabled = 5;
+ // Whether sticky battery saver should be deactivated once the battery level has reached the
+ // threshold specified by sticky_disable_level.
+ optional SettingProto sticky_auto_disable_enabled = 6;
+ // When a device is unplugged from a changer (or is rebooted), do not re-activate battery
+ // saver even if {@link #LOW_POWER_MODE_STICKY} is 1, if the battery level is equal to or
+ // above this threshold.
+ optional SettingProto sticky_auto_disable_level = 7;
}
optional LowPowerMode low_power_mode = 70;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index c0d6139..aaf6c63 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -530,7 +530,9 @@
optional SettingProto silence_gesture_enabled = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto theme_customization_overlay_packages = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto aware_enabled = 77 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 77;
+ // Next tag = 78;
}
diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto
index b70bb67..21717d8 100644
--- a/core/proto/android/server/connectivity/data_stall_event.proto
+++ b/core/proto/android/server/connectivity/data_stall_event.proto
@@ -41,7 +41,7 @@
RADIO_TECHNOLOGY_UMTS = 3;
RADIO_TECHNOLOGY_IS95A = 4;
RADIO_TECHNOLOGY_IS95B = 5;
- RADIO_TECHNOLOGY_1xRTT = 6;
+ RADIO_TECHNOLOGY_1XRTT = 6;
RADIO_TECHNOLOGY_EVDO_0 = 7;
RADIO_TECHNOLOGY_EVDO_A = 8;
RADIO_TECHNOLOGY_HSDPA = 9;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index cee556a..af0a942 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -350,4 +350,12 @@
// The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. This is a cached value, so it could
// be slightly different from what's in GlobalSettingsProto.LowPowerMode.
optional int32 setting_battery_saver_trigger_threshold = 11;
+
+ // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED. This is a cached value, so
+ // it could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+ optional bool setting_battery_saver_sticky_auto_disable_enabled = 12;
+
+ // The value of Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL. This is a cached value, so it
+ // could be slightly different from what's in GlobalSettingsProto.LowPowerMode.
+ optional int32 setting_battery_saver_sticky_auto_disable_threshold = 13;
}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 82460ec..a8e64c6 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -92,8 +92,7 @@
SET_UNINSTALL_BLOCKED = 67;
SET_PACKAGES_SUSPENDED = 68;
ON_LOCK_TASK_MODE_ENTERING = 69;
- ADD_CROSS_PROFILE_CALENDAR_PACKAGE = 70;
- REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE = 71;
+ SET_CROSS_PROFILE_CALENDAR_PACKAGES = 70;
GET_USER_PASSWORD_COMPLEXITY_LEVEL = 72;
INSTALL_SYSTEM_UPDATE = 73;
INSTALL_SYSTEM_UPDATE_ERROR = 74;
diff --git a/core/proto/android/view/remote_animation_target.proto b/core/proto/android/view/remote_animation_target.proto
index fb4d5bd..808c514 100644
--- a/core/proto/android/view/remote_animation_target.proto
+++ b/core/proto/android/view/remote_animation_target.proto
@@ -42,4 +42,6 @@
optional .android.graphics.PointProto position = 8;
optional .android.graphics.RectProto source_container_bounds = 9;
optional .android.app.WindowConfigurationProto window_configuration = 10;
+ optional .android.view.SurfaceControlProto start_leash = 11;
+ optional .android.graphics.RectProto start_bounds = 12;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c7cba62..f92df6a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1654,6 +1654,11 @@
<permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- #SystemApi @hide Allows privileged system APK to update Wifi usability stats and score.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8ef264a..de6468d 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -950,6 +950,17 @@
<p>The default value of this attribute is <code>false</code>. -->
<attr name="allowEmbedded" format="boolean" />
+ <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
+ top of the lock screen whenever the lockscreen is up and this activity has another
+ activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
+ is, this activity is only visible on the lock screen if there is another activity with
+ the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
+ lock screen. A use case for this is permission dialogs, that should only be visible on
+ the lock screen if their requesting activity is also visible.
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="inheritShowWhenLocked" format="boolean" />
+
<!-- Descriptive text for the associated data. -->
<attr name="description" format="reference" />
@@ -2415,18 +2426,6 @@
<attr name="showForAllUsers" />
<attr name="showWhenLocked" />
- <!-- @hide @SystemApi Specifies whether this {@link android.app.Activity} should be shown on
- top of the lock screen whenever the lockscreen is up and this activity has another
- activity behind it with the {@link android.R.attr#showWhenLocked} attribute set. That
- is, this activity is only visible on the lock screen if there is another activity with
- the {@link android.R.attr#showWhenLocked} attribute visible at the same time on the
- lock screen. A use case for this is permission dialogs, that should only be visible on
- the lock screen if their requesting activity is also visible.
-
- The default value of this attribute is <code>false</code>. -->
- <attr name="inheritShowWhenLocked" format="boolean" />
-
-
<attr name="inheritShowWhenLocked" />
<attr name="turnScreenOn" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a21306..bbc55a3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -126,6 +126,9 @@
be sent during a change to the audio output device. -->
<bool name="config_sendAudioBecomingNoisy">true</bool>
+ <!-- Whether Hearing Aid profile is supported -->
+ <bool name="config_hearing_aid_profile_supported">true</bool>
+
<!-- Flag to disable all transition animations -->
<bool name="config_disableTransitionAnimation">false</bool>
@@ -3759,4 +3762,7 @@
<!-- Whether cbrs is supported on the device or not -->
<bool translatable="false" name="config_cbrs_supported">false</bool>
+
+ <!-- Whether or not aware is enabled by default -->
+ <bool name="config_awareSettingAvailable">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 65a8959..a761baf 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3387,6 +3387,9 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
+ <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
+ <string name="captive_portal_logged_in_detailed">Connected</string>
+
<!-- A notification is shown when the user's softap config has been changed due to underlying
hardware restrictions. This is the notifications's title.
[CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 62baffd..cbb4cb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -437,6 +437,7 @@
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
<java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
<java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
+ <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -694,6 +695,7 @@
<java-symbol type="string" name="capability_title_canControlMagnification" />
<java-symbol type="string" name="capability_desc_canPerformGestures" />
<java-symbol type="string" name="capability_title_canPerformGestures" />
+ <java-symbol type="string" name="captive_portal_logged_in_detailed" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
@@ -3552,4 +3554,6 @@
<!-- For CBRS -->
<java-symbol type="bool" name="config_cbrs_supported" />
+
+ <java-symbol type="bool" name="config_awareSettingAvailable" />
</resources>
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 8604b0c..bdf3aa2 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -38,7 +38,6 @@
import android.content.pm.ServiceInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -501,7 +500,7 @@
}
@Override
- public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+ public void updateHttpProxy() throws RemoteException {
}
@Override
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index 3d7aab0..ad9814b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -48,21 +48,11 @@
}
/**
- * Detect when the org.apache.http.legacy is not on the bootclasspath.
- *
- * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
- */
- @Test
- public void detectWhenOAHLisOnBCP() {
- Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL());
- }
-
- /**
* Detect when the android.test.base is not on the bootclasspath.
*
* <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and
- * succeed otherwise. This allows a developer to ensure that the tests are being
+ * succeed otherwise. This allows a developer to ensure that the tests are being run in the
+ * correct environment.
*/
@Test
public void detectWhenATBisOnBCP() {
@@ -85,9 +75,7 @@
if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
expected.add(ANDROID_TEST_BASE);
}
- if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
- expected.add(ORG_APACHE_HTTP_LEGACY);
- }
+ expected.add(ORG_APACHE_HTTP_LEGACY);
PackageBuilder after = builder()
.targetSdkVersion(Build.VERSION_CODES.O)
@@ -98,30 +86,6 @@
/**
* Ensures that the {@link PackageBackwardCompatibility} uses
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}
- * when necessary.
- *
- * <p>More comprehensive tests for that class can be found in
- * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}.
- */
- @Test
- public void org_apache_http_legacy_in_usesLibraries() {
- Assume.assumeTrue("Test requires that "
- + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath",
- PackageBackwardCompatibility.bootClassPathContainsOAHL());
-
- PackageBuilder before = builder()
- .requiredLibraries(ORG_APACHE_HTTP_LEGACY);
-
- // org.apache.http.legacy should be removed from the libraries because it is provided
- // on the bootclasspath and providing both increases start up cost unnecessarily.
- PackageBuilder after = builder();
-
- checkBackwardsCompatibility(before, after);
- }
-
- /**
- * Ensures that the {@link PackageBackwardCompatibility} uses
* {@link RemoveUnnecessaryAndroidTestBaseLibrary}
* when necessary.
*
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 7b92cf5..300394d 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -527,12 +527,14 @@
R.raw.com_android_tzdata);
PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexFile, false);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
assertEquals(1, pi.applicationInfo.longVersionCode);
assertNull(pi.signingInfo);
pi = PackageParser.generatePackageInfoFromApex(apexFile, true);
assertEquals("com.google.android.tzdata", pi.packageName);
+ assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertEquals(1, pi.getLongVersionCode());
assertEquals(1, pi.applicationInfo.longVersionCode);
assertNotNull(pi.signingInfo);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8e16ddf..a010cb6 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -298,6 +298,7 @@
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS,
Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
@@ -485,6 +486,7 @@
Settings.Global.GUP_DEV_OPT_IN_APPS,
Settings.Global.GUP_DEV_OPT_OUT_APPS,
Settings.Global.GUP_BLACKLIST,
+ Settings.Global.GAME_DRIVER_WHITELIST,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
@@ -562,7 +564,9 @@
Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
Settings.Global.ENABLE_RADIO_BUG_DETECTION,
Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
- Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD);
+ Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
+ Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
+ Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
new file mode 100644
index 0000000..77e8d60
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Internal tests for {@link LongArrayQueue}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LongArrayQueueTest {
+
+ private LongArrayQueue mQueueUnderTest;
+
+ @Before
+ public void setUp() {
+ mQueueUnderTest = new LongArrayQueue();
+ }
+
+ @Test
+ public void removeFirstOnEmptyQueue() {
+ try {
+ mQueueUnderTest.removeFirst();
+ fail("removeFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.removeFirst();
+ fail("removeFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void addLastRemoveFirstFifo() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.removeFirst());
+ int n = 890;
+ int removes = 0;
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.addLast(i);
+ if ((i % 3) == 0) {
+ assertEquals(removes++, mQueueUnderTest.removeFirst());
+ }
+ }
+ while (removes < n) {
+ assertEquals(removes++, mQueueUnderTest.removeFirst());
+ }
+ }
+
+ @Test
+ public void peekFirstOnEmptyQueue() {
+ try {
+ mQueueUnderTest.peekFirst();
+ fail("peekFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.peekFirst();
+ fail("peekFirst() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void peekFirstChanges() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.peekFirst());
+ mQueueUnderTest.addLast(2);
+ mQueueUnderTest.addLast(3);
+ mQueueUnderTest.addLast(4);
+ // addLast() has no effect on peekFirst().
+ assertEquals(1, mQueueUnderTest.peekFirst());
+ mQueueUnderTest.removeFirst();
+ mQueueUnderTest.removeFirst();
+ assertEquals(3, mQueueUnderTest.peekFirst());
+ }
+
+ @Test
+ public void peekLastOnEmptyQueue() {
+ try {
+ mQueueUnderTest.peekLast();
+ fail("peekLast() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ mQueueUnderTest.addLast(5);
+ mQueueUnderTest.removeFirst();
+ try {
+ mQueueUnderTest.peekLast();
+ fail("peekLast() succeeded on an empty queue!");
+ } catch (NoSuchElementException e) {
+ }
+ }
+
+ @Test
+ public void peekLastChanges() {
+ mQueueUnderTest.addLast(1);
+ assertEquals(1, mQueueUnderTest.peekLast());
+ mQueueUnderTest.addLast(2);
+ mQueueUnderTest.addLast(3);
+ mQueueUnderTest.addLast(4);
+ assertEquals(4, mQueueUnderTest.peekLast());
+ mQueueUnderTest.removeFirst();
+ mQueueUnderTest.removeFirst();
+ // removeFirst() has no effect on peekLast().
+ assertEquals(4, mQueueUnderTest.peekLast());
+ }
+
+ @Test
+ public void peekFirstVsPeekLast() {
+ mQueueUnderTest.addLast(2);
+ assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ mQueueUnderTest.addLast(3);
+ assertNotEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ mQueueUnderTest.removeFirst();
+ assertEquals(mQueueUnderTest.peekFirst(), mQueueUnderTest.peekLast());
+ }
+
+ @Test
+ public void peekFirstVsRemoveFirst() {
+ int n = 25;
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.addLast(i + 1);
+ }
+ for (int i = 0; i < n; i++) {
+ long peekVal = mQueueUnderTest.peekFirst();
+ assertEquals(peekVal, mQueueUnderTest.removeFirst());
+ }
+ }
+
+ @Test
+ public void sizeOfEmptyQueue() {
+ assertEquals(0, mQueueUnderTest.size());
+ mQueueUnderTest = new LongArrayQueue(1000);
+ // capacity doesn't affect size.
+ assertEquals(0, mQueueUnderTest.size());
+ }
+
+ @Test
+ public void sizeAfterOperations() {
+ final int added = 1200;
+ for (int i = 0; i < added; i++) {
+ mQueueUnderTest.addLast(i);
+ }
+ // each add increments the size by 1.
+ assertEquals(added, mQueueUnderTest.size());
+ mQueueUnderTest.peekLast();
+ mQueueUnderTest.peekFirst();
+ // peeks don't change the size.
+ assertEquals(added, mQueueUnderTest.size());
+ final int removed = 345;
+ for (int i = 0; i < removed; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ // each remove decrements the size by 1.
+ assertEquals(added - removed, mQueueUnderTest.size());
+ mQueueUnderTest.clear();
+ // clear reduces the size to 0.
+ assertEquals(0, mQueueUnderTest.size());
+ }
+
+ @Test
+ public void getInvalidPositions() {
+ try {
+ mQueueUnderTest.get(0);
+ fail("get(0) succeeded on an empty queue!");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ int n = 520;
+ for (int i = 0; i < 2 * n; i++) {
+ mQueueUnderTest.addLast(i + 1);
+ }
+ for (int i = 0; i < n; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ try {
+ mQueueUnderTest.get(-3);
+ fail("get(-3) succeeded");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ assertEquals(n, mQueueUnderTest.size());
+ try {
+ mQueueUnderTest.get(n);
+ fail("get(" + n + ") succeeded on a queue with " + n + " elements");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ try {
+ mQueueUnderTest.get(n + 3);
+ fail("get(" + (n + 3) + ") succeeded on a queue with " + n + " elements");
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void getValidPositions() {
+ int added = 423;
+ int removed = 212;
+ for (int i = 0; i < added; i++) {
+ mQueueUnderTest.addLast(i);
+ }
+ for (int i = 0; i < removed; i++) {
+ mQueueUnderTest.removeFirst();
+ }
+ for (int i = 0; i < (added - removed); i++) {
+ assertEquals(removed + i, mQueueUnderTest.get(i));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
new file mode 100644
index 0000000..312e0e0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.view.contentcapture;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link ContentCaptureManager}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.ContentCaptureManagerTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCaptureManagerTest {
+
+ @Mock
+ private Context mMockContext;
+
+ private ContentCaptureManager mManager;
+
+ @Before
+ public void before() {
+ mManager = new ContentCaptureManager(mMockContext, null);
+ }
+
+ @Test
+ public void testRemoveUserData_invalid() {
+ assertThrows(NullPointerException.class, () -> mManager.removeUserData(null));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java
new file mode 100644
index 0000000..bebb2a8
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/UserDataRemovalRequestTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 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.view.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.net.Uri;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Unit test for {@link UserDataRemovalRequest}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:android.view.contentcapture.UserDataRemovalRequestTest}
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserDataRemovalRequestTest {
+
+ @Mock
+ private final Uri mUri = Uri.parse("content://com.example/");
+
+ private UserDataRemovalRequest.Builder mBuilder = new UserDataRemovalRequest.Builder();
+
+ @Test
+ public void testBuilder_addUri_invalid() {
+ assertThrows(NullPointerException.class, () -> mBuilder.addUri(null, false));
+ }
+
+ @Test
+ public void testBuilder_addUri_valid() {
+ assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+ assertThat(mBuilder.addUri(Uri.parse("content://com.example2"), true)).isNotNull();
+ }
+
+ @Test
+ public void testBuilder_addUriAfterForEverything() {
+ assertThat(mBuilder.forEverything()).isNotNull();
+ assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+ }
+
+ @Test
+ public void testBuilder_forEverythingAfterAddingUri() {
+ assertThat(mBuilder.addUri(mUri, false)).isNotNull();
+ assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+ }
+
+ @Test
+ public void testBuild_invalid() {
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+ }
+
+ @Test
+ public void testBuild_valid() {
+ assertThat(new UserDataRemovalRequest.Builder().forEverything().build())
+ .isNotNull();
+ assertThat(new UserDataRemovalRequest.Builder().addUri(mUri, false).build())
+ .isNotNull();
+ }
+
+ @Test
+ public void testNoMoreInteractionsAfterBuild() {
+ assertThat(mBuilder.forEverything().build()).isNotNull();
+
+ assertThrows(IllegalStateException.class, () -> mBuilder.addUri(mUri, false));
+ assertThrows(IllegalStateException.class, () -> mBuilder.forEverything());
+ assertThrows(IllegalStateException.class, () -> mBuilder.build());
+
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 7009fb2..4d78e40 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -262,6 +262,9 @@
assertEquals(
context.getString(com.android.internal.R.string.translate),
classification.getActions().get(0).getTitle());
+ Intent intent = (Intent) classification.getExtras()
+ .getParcelableArrayList(TextClassifierImpl.ACTIONS_INTENTS).get(0);
+ assertEquals(Intent.ACTION_TRANSLATE, intent.getAction());
LocaleList.setDefault(originalLocales);
}
diff --git a/media/Android.bp b/media/Android.bp
index 0eb86ac..88ed9c6a 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -1,25 +1,59 @@
java_library {
- name: "media1",
+ name: "updatable-media1",
srcs: [
":media1-srcs",
+ ":framework-media-annotation-srcs",
],
+ aidl: {
+ export_include_dirs: [
+ "apex/java",
+ ],
+
+ // TODO: find out a way to include only the necessary aidl files instead of dirs.
+ include_dirs: [
+ "frameworks/base/core/java",
+ "frameworks/base/media/java",
+ ],
+ },
+
+ installable: true,
+
+ // Make sure that the implementaion only relies on SDK or system APIs.
sdk_version: "system_current",
}
-filegroup {
- name: "media1-srcs",
+java_library {
+ name: "updatable-mediasession2",
+
srcs: [
- "java/android/media/session/MediaSessionProviderService.java",
+ ":mediasession2-srcs",
+ ":framework-media-annotation-srcs",
],
+
+ aidl: {
+ export_include_dirs: [
+ "apex/java",
+ ],
+
+ // TODO: find out a way to include only the necessary aidl files instead of dirs.
+ include_dirs: [
+ "frameworks/base/core/java",
+ ],
+ },
+
+ installable: true,
+
+ // Make sure that the implementaion only relies on SDK or system APIs.
+ sdk_version: "system_current",
}
java_library {
name: "updatable-media",
srcs: [
- ":media2-srcs",
+ ":mediaplayer2-srcs",
":framework-media-annotation-srcs",
],
@@ -34,7 +68,88 @@
}
filegroup {
- name: "media2-srcs",
+ name: "media-srcs-without-aidls",
+ srcs : [
+ ":media1-srcs-without-aidls",
+ ":mediasession2-srcs-without-aidls",
+ ":mediaplayer2-srcs",
+ ],
+}
+
+filegroup {
+ name: "media1-srcs",
+ srcs: [
+ "apex/java/android/media/MediaMetadata.java",
+ "apex/java/android/media/MediaParceledListSlice.java",
+ "apex/java/android/media/VolumeProvider.java",
+ "apex/java/android/media/browse/MediaBrowser.java",
+ "apex/java/android/media/browse/MediaBrowserUtils.java",
+ "apex/java/android/media/session/ControllerCallbackLink.java",
+ "apex/java/android/media/session/ControllerLink.java",
+ "apex/java/android/media/session/ISession.aidl",
+ "apex/java/android/media/session/ISessionCallback.aidl",
+ "apex/java/android/media/session/ISessionController.aidl",
+ "apex/java/android/media/session/ISessionControllerCallback.aidl",
+ "apex/java/android/media/session/MediaController.java",
+ "apex/java/android/media/session/MediaSessionEngine.java",
+ "apex/java/android/media/session/MediaSessionProviderService.java",
+ "apex/java/android/media/session/PlaybackState.java",
+ "apex/java/android/media/session/SessionCallbackLink.java",
+ "apex/java/android/media/session/SessionLink.java",
+ "apex/java/android/service/media/IMediaBrowserService.aidl",
+ "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+ "apex/java/android/service/media/MediaBrowserService.java",
+ ],
+}
+
+filegroup {
+ name: "media1-srcs-without-aidls",
+ srcs: [
+ ":media1-srcs",
+ ],
+ exclude_srcs: [
+ "apex/java/android/media/session/ISession.aidl",
+ "apex/java/android/media/session/ISessionCallback.aidl",
+ "apex/java/android/media/session/ISessionController.aidl",
+ "apex/java/android/media/session/ISessionControllerCallback.aidl",
+ "apex/java/android/service/media/IMediaBrowserService.aidl",
+ "apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl",
+ ],
+}
+
+filegroup {
+ name: "mediasession2-srcs",
+ srcs: [
+ "apex/java/android/media/Controller2Link.java",
+ "apex/java/android/media/IMediaController2.aidl",
+ "apex/java/android/media/IMediaSession2.aidl",
+ "apex/java/android/media/IMediaSession2Service.aidl",
+ "apex/java/android/media/MediaConstants.java",
+ "apex/java/android/media/MediaController2.java",
+ "apex/java/android/media/MediaItem2.java",
+ "apex/java/android/media/MediaSession2.java",
+ "apex/java/android/media/MediaSession2Service.java",
+ "apex/java/android/media/Session2Command.java",
+ "apex/java/android/media/Session2CommandGroup.java",
+ "apex/java/android/media/Session2Link.java",
+ "apex/java/android/media/Session2Token.java",
+ ],
+}
+
+filegroup {
+ name: "mediasession2-srcs-without-aidls",
+ srcs: [
+ ":mediasession2-srcs",
+ ],
+ exclude_srcs: [
+ "apex/java/android/media/IMediaController2.aidl",
+ "apex/java/android/media/IMediaSession2.aidl",
+ "apex/java/android/media/IMediaSession2Service.aidl",
+ ],
+}
+
+filegroup {
+ name: "mediaplayer2-srcs",
srcs: [
"apex/java/android/media/CloseGuard.java",
"apex/java/android/media/DataSourceCallback.java",
diff --git a/media/java/android/media/Controller2Link.aidl b/media/apex/java/android/media/Controller2Link.aidl
similarity index 100%
rename from media/java/android/media/Controller2Link.aidl
rename to media/apex/java/android/media/Controller2Link.aidl
diff --git a/media/java/android/media/Controller2Link.java b/media/apex/java/android/media/Controller2Link.java
similarity index 100%
rename from media/java/android/media/Controller2Link.java
rename to media/apex/java/android/media/Controller2Link.java
diff --git a/media/java/android/media/IMediaController2.aidl b/media/apex/java/android/media/IMediaController2.aidl
similarity index 100%
rename from media/java/android/media/IMediaController2.aidl
rename to media/apex/java/android/media/IMediaController2.aidl
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/apex/java/android/media/IMediaSession2.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2.aidl
rename to media/apex/java/android/media/IMediaSession2.aidl
diff --git a/media/java/android/media/IMediaSession2Service.aidl b/media/apex/java/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from media/java/android/media/IMediaSession2Service.aidl
rename to media/apex/java/android/media/IMediaSession2Service.aidl
diff --git a/media/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
similarity index 100%
rename from media/java/android/media/MediaConstants.java
rename to media/apex/java/android/media/MediaConstants.java
diff --git a/media/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
similarity index 100%
rename from media/java/android/media/MediaController2.java
rename to media/apex/java/android/media/MediaController2.java
diff --git a/media/java/android/media/MediaItem2.java b/media/apex/java/android/media/MediaItem2.java
similarity index 100%
rename from media/java/android/media/MediaItem2.java
rename to media/apex/java/android/media/MediaItem2.java
diff --git a/media/java/android/media/MediaMetadata.aidl b/media/apex/java/android/media/MediaMetadata.aidl
similarity index 100%
rename from media/java/android/media/MediaMetadata.aidl
rename to media/apex/java/android/media/MediaMetadata.aidl
diff --git a/media/java/android/media/MediaMetadata.java b/media/apex/java/android/media/MediaMetadata.java
similarity index 98%
rename from media/java/android/media/MediaMetadata.java
rename to media/apex/java/android/media/MediaMetadata.java
index a3d75a3..dea98d5 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/apex/java/android/media/MediaMetadata.java
@@ -250,16 +250,16 @@
* second line for media described by this metadata this should be preferred
* to other fields if present.
*/
- public static final String METADATA_KEY_DISPLAY_SUBTITLE
- = "android.media.metadata.DISPLAY_SUBTITLE";
+ public static final String METADATA_KEY_DISPLAY_SUBTITLE =
+ "android.media.metadata.DISPLAY_SUBTITLE";
/**
* A description that is suitable for display to the user. When displaying
* more information for media described by this metadata this should be
* preferred to other fields if present.
*/
- public static final String METADATA_KEY_DISPLAY_DESCRIPTION
- = "android.media.metadata.DISPLAY_DESCRIPTION";
+ public static final String METADATA_KEY_DISPLAY_DESCRIPTION =
+ "android.media.metadata.DISPLAY_DESCRIPTION";
/**
* An icon or thumbnail that is suitable for display to the user. When
@@ -270,8 +270,8 @@
* if it is too large. For higher resolution artwork
* {@link #METADATA_KEY_DISPLAY_ICON_URI} should be used instead.
*/
- public static final String METADATA_KEY_DISPLAY_ICON
- = "android.media.metadata.DISPLAY_ICON";
+ public static final String METADATA_KEY_DISPLAY_ICON =
+ "android.media.metadata.DISPLAY_ICON";
/**
* A Uri formatted String for an icon or thumbnail that is suitable for
@@ -285,8 +285,8 @@
* {@link ContentResolver#EXTRA_SIZE} for retrieving scaled artwork through
* {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}.
*/
- public static final String METADATA_KEY_DISPLAY_ICON_URI
- = "android.media.metadata.DISPLAY_ICON_URI";
+ public static final String METADATA_KEY_DISPLAY_ICON_URI =
+ "android.media.metadata.DISPLAY_ICON_URI";
/**
* A String key for identifying the content. This value is specific to the
@@ -320,8 +320,8 @@
* <li>{@link MediaDescription#BT_FOLDER_TYPE_YEARS}</li>
* </ul>
*/
- public static final String METADATA_KEY_BT_FOLDER_TYPE
- = "android.media.metadata.BT_FOLDER_TYPE";
+ public static final String METADATA_KEY_BT_FOLDER_TYPE =
+ "android.media.metadata.BT_FOLDER_TYPE";
private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = {
METADATA_KEY_TITLE,
diff --git a/media/java/android/media/MediaParceledListSlice.aidl b/media/apex/java/android/media/MediaParceledListSlice.aidl
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.aidl
rename to media/apex/java/android/media/MediaParceledListSlice.aidl
diff --git a/media/java/android/media/MediaParceledListSlice.java b/media/apex/java/android/media/MediaParceledListSlice.java
similarity index 100%
rename from media/java/android/media/MediaParceledListSlice.java
rename to media/apex/java/android/media/MediaParceledListSlice.java
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index aa79c41..a2feec2 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -26,6 +26,7 @@
import android.content.res.AssetFileDescriptor;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.MediaDrm.KeyRequest;
import android.media.MediaPlayer2.DrmInfo;
import android.media.MediaPlayer2Proto.PlayerMessage;
import android.media.MediaPlayer2Proto.Value;
@@ -76,6 +77,8 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -298,7 +301,7 @@
private volatile float mVolume = 1.0f;
private VideoSize mVideoSize = new VideoSize(0, 0);
- private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool();
+ private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
// Creating a dummy audio track, used for keeping session id alive
private final Object mSessionIdLock = new Object();
@@ -402,7 +405,7 @@
// Modular DRM clean up
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords.clear();
+ mDrmEventCallback = null;
}
native_release();
@@ -2293,6 +2296,7 @@
private static final int MEDIA_PAUSED = 7;
private static final int MEDIA_STOPPED = 8;
private static final int MEDIA_SKIPPED = 9;
+ private static final int MEDIA_DRM_PREPARED = 10;
private static final int MEDIA_NOTIFY_TIME = 98;
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
@@ -2330,7 +2334,16 @@
switch(msg.what) {
case MEDIA_PREPARED:
+ case MEDIA_DRM_PREPARED:
{
+ sourceInfo.mPrepareBarrier--;
+ if (sourceInfo.mPrepareBarrier > 0) {
+ break;
+ } else if (sourceInfo.mPrepareBarrier < 0) {
+ Log.w(TAG, "duplicated (drm) prepared events");
+ break;
+ }
+
if (dsd != null) {
sendEvent(new EventNotifier() {
@Override
@@ -2387,14 +2400,42 @@
}
// notifying the client outside the lock
+ DrmPreparationInfo drmPrepareInfo = null;
if (drmInfo != null) {
- sendDrmEvent(new DrmEventNotifier() {
+ try {
+ drmPrepareInfo = sendDrmEventWait(
+ new DrmEventNotifier<DrmPreparationInfo>() {
+ @Override
+ public DrmPreparationInfo notifyWait(
+ DrmEventCallback callback) {
+ return callback.onDrmInfo(mMediaPlayer, dsd,
+ drmInfo);
+ }
+ });
+ } catch (InterruptedException | ExecutionException
+ | TimeoutException e) {
+ Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e);
+ }
+ }
+ if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) {
+ sourceInfo.mPrepareBarrier++;
+ final Task prepareDrmTask;
+ prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID);
+ mTaskHandler.post(new Runnable() {
@Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmInfo(
- mMediaPlayer, dsd, drmInfo);
+ public void run() {
+ // Run as simple Runnable, not Task
+ try {
+ prepareDrmTask.process();
+ } catch (NoDrmSchemeException | IOException e) {
+ final String errMsg;
+ errMsg = "Unexpected Exception during prepareDrm";
+ throw new RuntimeException(errMsg, e);
+ }
}
});
+ } else {
+ Log.w(TAG, "No valid DrmPreparationInfo set");
}
} else {
Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
@@ -2892,7 +2933,8 @@
private void sendDrmEvent(final DrmEventNotifier notifier) {
synchronized (mDrmEventCallbackLock) {
try {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+ if (cb != null) {
cb.first.execute(() -> notifier.notify(cb.second));
}
} catch (RejectedExecutionException e) {
@@ -2903,13 +2945,18 @@
}
private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
- throws InterruptedException, ExecutionException {
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return sendDrmEventWait(notifier, 0);
+ }
+
+ private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs)
+ throws InterruptedException, ExecutionException, TimeoutException {
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords.get(0);
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
+ Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
+ if (cb != null) {
CompletableFuture<T> ret = new CompletableFuture<>();
cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
- return ret.get();
+ return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS);
}
}
return null;
@@ -3388,8 +3435,8 @@
private Map<String, String> mOptionalParameters;
/**
- * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved from
- * the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
+ * Set UUID of the crypto scheme selected to decrypt content. An UUID can be retrieved
+ * from the source listening to {@link MediaPlayer2.DrmEventCallback#onDrmInfo}.
*
* @param uuid of selected crypto scheme
* @return this
@@ -3401,11 +3448,12 @@
/**
* Set identifier of a persisted offline key obtained from
- * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared(MediaPlayer2, DataSourceDesc, int, byte[])}.
+ * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}.
*
* A {@code keySetId} can be used to restore persisted offline keys into a new playback
- * session of a DRM protected data source. When {@code keySetId} is set, {@code initData},
- * {@code mimeType}, {@code keyType}, {@code optionalParameters} are ignored.
+ * session of a DRM protected data source. When {@code keySetId} is set,
+ * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are
+ * ignored.
*
* @param keySetId identifier of a persisted offline key
* @return this
@@ -3455,24 +3503,24 @@
}
/**
- * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent to
- * the license server.
+ * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent
+ * to the license server.
*
* @param optionalParameters optional parameters to be included in a key request
* @return this
*/
- public Builder setOptionalParameters(
- @Nullable Map<String, String> optionalParameters) {
+ public Builder setOptionalParameters(@Nullable Map<String, String> optionalParameters) {
this.mOptionalParameters = optionalParameters;
return this;
}
/**
- * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the settings of this builder
+ * @return an immutable {@link MediaPlayer2.DrmPreparationInfo} representing the
+ * settings of this builder
*/
public MediaPlayer2.DrmPreparationInfo build() {
- return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType, mKeyType,
- mOptionalParameters);
+ return new MediaPlayer2.DrmPreparationInfo(mUUID, mKeySetId, mInitData, mMimeType,
+ mKeyType, mOptionalParameters);
}
}
@@ -3494,6 +3542,20 @@
this.mOptionalParameters = optionalParameters;
}
+ boolean isValid() {
+ if (mUUID == null) {
+ return false;
+ }
+ if (mKeySetId != null) {
+ // offline restore case
+ return true;
+ }
+ if (mInitData != null && mMimeType != null) {
+ // new streaming license case
+ return true;
+ }
+ return false;
+ }
}
/**
@@ -3501,6 +3563,7 @@
* DRM events.
*/
public static class DrmEventCallback {
+
/**
* Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
* bundles DRM initialization parameters.
@@ -3517,21 +3580,6 @@
}
/**
- * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
- * {@code dsd}
- *
- * @param mp the {@code MediaPlayer2} associated with this callback
- * @param dsd the {@link DataSourceDesc} of this data source
- * @param status the result of DRM preparation.
- * @param keySetId optional identifier that can be used to restore DRM playback initiated
- * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
- *
- * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
- */
- public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
- @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
-
- /**
* Called to give the app the opportunity to configure DRM before the session is created.
*
* This facilitates configuration of the properties, like 'securityLevel', which
@@ -3567,11 +3615,25 @@
return null;
}
+ /**
+ * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
+ * {@code dsd} or if there is an error during DRM preparation
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param dsd the {@link DataSourceDesc} of this data source
+ * @param status the result of DRM preparation.
+ * @param keySetId optional identifier that can be used to restore DRM playback initiated
+ * with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
+ *
+ * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
+ */
+ public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
+ @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
+
}
private final Object mDrmEventCallbackLock = new Object();
- private List<Pair<Executor, DrmEventCallback>> mDrmEventCallbackRecords =
- new ArrayList<Pair<Executor, DrmEventCallback>>();
+ private Pair<Executor, DrmEventCallback> mDrmEventCallback;
/**
* Registers the callback to be invoked for various DRM events.
@@ -3590,25 +3652,17 @@
"Illegal null Executor for the EventCallback");
}
synchronized (mDrmEventCallbackLock) {
- mDrmEventCallbackRecords = Collections.singletonList(
- new Pair<Executor, DrmEventCallback>(executor, eventCallback));
+ mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback);
}
}
/**
- * Unregisters the {@link DrmEventCallback}.
- *
- * @param eventCallback the callback to be unregistered
- * @hide
+ * Clear the {@link DrmEventCallback}.
*/
// This is a synchronous call.
- public void unregisterDrmEventCallback(DrmEventCallback eventCallback) {
+ public void clearDrmEventCallback() {
synchronized (mDrmEventCallbackLock) {
- for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) {
- if (cb.second == eventCallback) {
- mDrmEventCallbackRecords.remove(cb);
- }
- }
+ mDrmEventCallback = null;
}
}
@@ -3651,6 +3705,18 @@
*/
public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
+ /**
+ * Restoring persisted offline keys failed.
+ * @hide
+ */
+ public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6;
+
+ /**
+ * Error during key request/response exchange with license server.
+ * @hide
+ */
+ public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7;
+
/** @hide */
@IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = {
PREPARE_DRM_STATUS_SUCCESS,
@@ -3659,6 +3725,8 @@
PREPARE_DRM_STATUS_PREPARATION_ERROR,
PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
PREPARE_DRM_STATUS_RESOURCE_BUSY,
+ PREPARE_DRM_STATUS_RESTORE_ERROR,
+ PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PrepareDrmStatusCode {}
@@ -3747,12 +3815,16 @@
*/
// This is an asynchronous call.
public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
- return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
+ return addTask(newPrepareDrmTask(dsd, uuid));
+ }
+
+ private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) {
+ return new Task(CALL_COMPLETED_PREPARE_DRM, true) {
@Override
void process() {
final SourceInfo sourceInfo = getSourceInfo(dsd);
int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
- boolean sendEvent = true;
+ boolean finishPrepare = true;
if (sourceInfo == null) {
Log.e(TAG, "prepareDrm(): DataSource not found.");
@@ -3780,8 +3852,8 @@
status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
if (status == PREPARE_DRM_STATUS_SUCCESS) {
- // DrmEventCallback will be fired in provisioning
- sendEvent = false;
+ // License will be setup in provisioning
+ finishPrepare = false;
} else {
synchronized (sourceInfo.mDrmHandle) {
sourceInfo.mDrmHandle.cleanDrmObj();
@@ -3808,23 +3880,16 @@
status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
}
- if (sendEvent) {
- final int prepareDrmStatus = status;
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(MediaPlayer2.this, dsd, prepareDrmStatus,
- /* TODO: keySetId */ null);
- }
- });
-
+ if (finishPrepare) {
+ sourceInfo.mDrmHandle.finishPrepare(status);
synchronized (mTaskLock) {
mCurrentTask = null;
processPendingTask_l();
}
}
+
}
- });
+ };
}
/**
@@ -4417,7 +4482,7 @@
};
// Modular DRM
- final class DrmHandle {
+ private class DrmHandle {
static final int PROVISION_TIMEOUT_MS = 60000;
@@ -4432,6 +4497,7 @@
boolean mDrmProvisioningInProgress;
boolean mPrepareDrmInProgress;
Future<?> mProvisionResult;
+ DrmPreparationInfo mPrepareInfo;
//--- guarded by |this| end
DrmHandle(DataSourceDesc dsd, long srcId) {
@@ -4441,7 +4507,7 @@
void prepare(UUID uuid) throws UnsupportedSchemeException,
ResourceBusyException, NotProvisionedException, InterruptedException,
- ExecutionException {
+ ExecutionException, TimeoutException {
Log.v(TAG, "prepareDrm: uuid: " + uuid);
synchronized (this) {
@@ -4580,7 +4646,7 @@
// networking in a background thread
mDrmProvisioningInProgress = true;
- mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
+ mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
return PREPARE_DRM_STATUS_SUCCESS;
}
@@ -4654,14 +4720,7 @@
} // synchronized
// calling the callback outside the lock
- final int finalStatus = status;
- sendDrmEvent(new DrmEventNotifier() {
- @Override
- public void notify(DrmEventCallback callback) {
- callback.onDrmPrepared(
- MediaPlayer2.this, mDSD, finalStatus, /* TODO: keySetId */ null);
- }
- });
+ finishPrepare(status);
synchronized (mTaskLock) {
if (mCurrentTask != null
@@ -4703,6 +4762,93 @@
return success;
}
+ synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) {
+ if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) {
+ return false;
+ }
+ mPrepareInfo = prepareInfo;
+ return true;
+ }
+
+ void finishPrepare(int status) {
+ if (status != PREPARE_DRM_STATUS_SUCCESS) {
+ notifyPrepared(status, null);
+ return;
+ }
+
+ if (mPrepareInfo == null) {
+ // Deprecated: this can only happen when using MediaPlayer Version 1 APIs
+ notifyPrepared(status, null);
+ return;
+ }
+
+ final byte[] keySetId = mPrepareInfo.mKeySetId;
+ if (keySetId != null) {
+ try {
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+ notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+ } catch (Exception e) {
+ notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId);
+ }
+ return;
+ }
+
+ sDrmThreadPool.submit(newKeyExchangeTask());
+ }
+
+ Runnable newKeyExchangeTask() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ final byte[] initData = mPrepareInfo.mInitData;
+ final String mimeType = mPrepareInfo.mMimeType;
+ final int keyType = mPrepareInfo.mKeyType;
+ final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters;
+ byte[] keySetId = null;
+ try {
+ KeyRequest req;
+ req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams);
+ byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() {
+ @Override
+ public byte[] notifyWait(DrmEventCallback callback) {
+ final MediaPlayer2 mp = MediaPlayer2.this;
+ return callback.onDrmKeyRequest(mp, mDSD, req);
+ }
+ });
+ keySetId = provideDrmKeyResponse(null, response);
+ } catch (Exception e) {
+ }
+ if (keySetId == null) {
+ notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null);
+ } else {
+ notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
+ }
+ }
+ };
+ }
+
+ void notifyPrepared(final int status, byte[] keySetId) {
+
+ Message msg;
+ if (status == PREPARE_DRM_STATUS_SUCCESS) {
+ msg = mTaskHandler.obtainMessage(
+ MEDIA_DRM_PREPARED, 0, 0, null);
+ } else {
+ msg = mTaskHandler.obtainMessage(
+ MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
+ }
+ mTaskHandler.sendMessage(msg);
+
+ sendDrmEvent(new DrmEventNotifier() {
+ @Override
+ public void notify(DrmEventCallback callback) {
+ callback.onDrmPrepared(MediaPlayer2.this, mDSD, status,
+ keySetId);
+ }
+ });
+
+ }
+
void cleanDrmObj() {
// the caller holds mDrmLock
Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
@@ -4768,6 +4914,7 @@
// set to false to avoid duplicate release calls
this.mActiveDrmUUID = null;
+ native_releaseDrm(mSrcId);
cleanDrmObj();
} // synchronized
}
@@ -4924,6 +5071,7 @@
final long mId = mSrcIdGenerator.getAndIncrement();
AtomicInteger mBufferedPercentage = new AtomicInteger(0);
boolean mClosed = false;
+ int mPrepareBarrier = 1;
// m*AsNextSource (below) only applies to pending data sources in the playlist;
// the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
@@ -5022,7 +5170,7 @@
if (sourceInfo != null) {
sourceInfo.close();
Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
- mDrmThreadPool.submit(task);
+ sDrmThreadPool.submit(task);
}
}
diff --git a/media/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
similarity index 100%
rename from media/java/android/media/MediaSession2.java
rename to media/apex/java/android/media/MediaSession2.java
diff --git a/media/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
similarity index 100%
rename from media/java/android/media/MediaSession2Service.java
rename to media/apex/java/android/media/MediaSession2Service.java
diff --git a/media/java/android/media/Session2Command.aidl b/media/apex/java/android/media/Session2Command.aidl
similarity index 100%
rename from media/java/android/media/Session2Command.aidl
rename to media/apex/java/android/media/Session2Command.aidl
diff --git a/media/java/android/media/Session2Command.java b/media/apex/java/android/media/Session2Command.java
similarity index 100%
rename from media/java/android/media/Session2Command.java
rename to media/apex/java/android/media/Session2Command.java
diff --git a/media/java/android/media/Session2CommandGroup.java b/media/apex/java/android/media/Session2CommandGroup.java
similarity index 96%
rename from media/java/android/media/Session2CommandGroup.java
rename to media/apex/java/android/media/Session2CommandGroup.java
index a189c26..2dab697 100644
--- a/media/java/android/media/Session2CommandGroup.java
+++ b/media/apex/java/android/media/Session2CommandGroup.java
@@ -71,11 +71,10 @@
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
Session2CommandGroup(Parcel in) {
- Session2Command[] commands = in.readParcelableArray(
- Session2Command.class.getClassLoader(), Session2Command.class);
+ Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader());
if (commands != null) {
- for (Session2Command command : commands) {
- mCommands.add(command);
+ for (Parcelable command : commands) {
+ mCommands.add((Session2Command) command);
}
}
}
diff --git a/media/java/android/media/Session2Link.java b/media/apex/java/android/media/Session2Link.java
similarity index 100%
rename from media/java/android/media/Session2Link.java
rename to media/apex/java/android/media/Session2Link.java
diff --git a/media/java/android/media/Session2Token.aidl b/media/apex/java/android/media/Session2Token.aidl
similarity index 100%
rename from media/java/android/media/Session2Token.aidl
rename to media/apex/java/android/media/Session2Token.aidl
diff --git a/media/java/android/media/Session2Token.java b/media/apex/java/android/media/Session2Token.java
similarity index 100%
rename from media/java/android/media/Session2Token.java
rename to media/apex/java/android/media/Session2Token.java
diff --git a/media/java/android/media/VolumeProvider.java b/media/apex/java/android/media/VolumeProvider.java
similarity index 97%
rename from media/java/android/media/VolumeProvider.java
rename to media/apex/java/android/media/VolumeProvider.java
index 0297406..49202ee 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/apex/java/android/media/VolumeProvider.java
@@ -158,7 +158,10 @@
* @hide
*/
@SystemApi
- public static abstract class Callback {
+ public abstract static class Callback {
+ /**
+ * Called when volume changed.
+ */
public abstract void onVolumeChanged(VolumeProvider volumeProvider);
}
}
diff --git a/media/java/android/media/browse/MediaBrowser.aidl b/media/apex/java/android/media/browse/MediaBrowser.aidl
similarity index 100%
rename from media/java/android/media/browse/MediaBrowser.aidl
rename to media/apex/java/android/media/browse/MediaBrowser.aidl
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/apex/java/android/media/browse/MediaBrowser.java
similarity index 98%
rename from media/java/android/media/browse/MediaBrowser.java
rename to media/apex/java/android/media/browse/MediaBrowser.java
index b1b14c6..2dffef9 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/apex/java/android/media/browse/MediaBrowser.java
@@ -284,8 +284,8 @@
*/
public @NonNull ComponentName getServiceComponent() {
if (!isConnected()) {
- throw new IllegalStateException("getServiceComponent() called while not connected" +
- " (state=" + mState + ")");
+ throw new IllegalStateException("getServiceComponent() called while not connected"
+ + " (state=" + mState + ")");
}
return mServiceComponent;
}
@@ -331,7 +331,7 @@
*
* @throws IllegalStateException if not connected.
*/
- public @NonNull MediaSession.Token getSessionToken() {
+ public @NonNull MediaSession.Token getSessionToken() {
if (!isConnected()) {
throw new IllegalStateException("getSessionToken() called while not connected (state="
+ mState + ")");
@@ -464,7 +464,7 @@
cb.onError(mediaId);
return;
}
- cb.onItemLoaded((MediaItem)item);
+ cb.onItemLoaded((MediaItem) item);
}
};
try {
@@ -575,7 +575,7 @@
}
}
- private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
+ private void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
final String root, final MediaSession.Token session, final Bundle extra) {
mHandler.post(new Runnable() {
@Override
@@ -625,7 +625,7 @@
});
}
- private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
+ private void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -652,7 +652,7 @@
});
}
- private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
+ private void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
final String parentId, final MediaParceledListSlice list, final Bundle options) {
mHandler.post(new Runnable() {
@Override
@@ -745,7 +745,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
+ @IntDef(flag = true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
public @interface Flags { }
/**
@@ -886,7 +886,7 @@
/**
* Callbacks for subscription related events.
*/
- public static abstract class SubscriptionCallback {
+ public abstract static class SubscriptionCallback {
Binder mToken;
public SubscriptionCallback() {
@@ -947,7 +947,7 @@
/**
* Callback for receiving the result of {@link #getItem}.
*/
- public static abstract class ItemCallback {
+ public abstract static class ItemCallback {
/**
* Called when the item has been returned by the connected service.
*
@@ -1078,7 +1078,7 @@
private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
private WeakReference<MediaBrowser> mMediaBrowser;
- public ServiceCallbacks(MediaBrowser mediaBrowser) {
+ ServiceCallbacks(MediaBrowser mediaBrowser) {
mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
}
@@ -1125,7 +1125,7 @@
private final List<SubscriptionCallback> mCallbacks;
private final List<Bundle> mOptionsList;
- public Subscription() {
+ Subscription() {
mCallbacks = new ArrayList<>();
mOptionsList = new ArrayList<>();
}
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/apex/java/android/media/browse/MediaBrowserUtils.java
similarity index 94%
rename from media/java/android/media/browse/MediaBrowserUtils.java
rename to media/apex/java/android/media/browse/MediaBrowserUtils.java
index 2943e60..19d9f00 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/apex/java/android/media/browse/MediaBrowserUtils.java
@@ -22,6 +22,9 @@
* @hide
*/
public class MediaBrowserUtils {
+ /**
+ * Compares whether two bundles are the same.
+ */
public static boolean areSameOptions(Bundle options1, Bundle options2) {
if (options1 == options2) {
return true;
@@ -39,6 +42,9 @@
}
}
+ /**
+ * Returnes true if the page options has duplicated items.
+ */
public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
diff --git a/media/java/android/media/session/ControllerCallbackLink.aidl b/media/apex/java/android/media/session/ControllerCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.aidl
rename to media/apex/java/android/media/session/ControllerCallbackLink.aidl
diff --git a/media/java/android/media/session/ControllerCallbackLink.java b/media/apex/java/android/media/session/ControllerCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerCallbackLink.java
rename to media/apex/java/android/media/session/ControllerCallbackLink.java
diff --git a/media/java/android/media/session/ControllerLink.aidl b/media/apex/java/android/media/session/ControllerLink.aidl
similarity index 100%
rename from media/java/android/media/session/ControllerLink.aidl
rename to media/apex/java/android/media/session/ControllerLink.aidl
diff --git a/media/java/android/media/session/ControllerLink.java b/media/apex/java/android/media/session/ControllerLink.java
similarity index 100%
rename from media/java/android/media/session/ControllerLink.java
rename to media/apex/java/android/media/session/ControllerLink.java
diff --git a/media/java/android/media/session/ISession.aidl b/media/apex/java/android/media/session/ISession.aidl
similarity index 100%
rename from media/java/android/media/session/ISession.aidl
rename to media/apex/java/android/media/session/ISession.aidl
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/apex/java/android/media/session/ISessionCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionCallback.aidl
rename to media/apex/java/android/media/session/ISessionCallback.aidl
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/apex/java/android/media/session/ISessionController.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionController.aidl
rename to media/apex/java/android/media/session/ISessionController.aidl
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/apex/java/android/media/session/ISessionControllerCallback.aidl
similarity index 100%
rename from media/java/android/media/session/ISessionControllerCallback.aidl
rename to media/apex/java/android/media/session/ISessionControllerCallback.aidl
diff --git a/media/java/android/media/session/MediaController.aidl b/media/apex/java/android/media/session/MediaController.aidl
similarity index 100%
rename from media/java/android/media/session/MediaController.aidl
rename to media/apex/java/android/media/session/MediaController.aidl
diff --git a/media/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java
similarity index 99%
rename from media/java/android/media/session/MediaController.java
rename to media/apex/java/android/media/session/MediaController.java
index 057c9cb..d43acf4 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -206,6 +206,7 @@
} catch (RuntimeException e) {
Log.wtf(TAG, "Error calling adjustVolumeBy", e);
}
+ break;
}
case KeyEvent.ACTION_UP: {
@@ -319,7 +320,7 @@
*
* @return The current set of flags for the session.
*/
- public @MediaSession.SessionFlags long getFlags() {
+ public long getFlags() {
try {
return mSessionBinder.getFlags();
} catch (RuntimeException e) {
@@ -582,7 +583,7 @@
return null;
}
- private final void postMessage(int what, Object obj, Bundle data) {
+ private void postMessage(int what, Object obj, Bundle data) {
synchronized (mLock) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).post(what, obj, data);
@@ -594,7 +595,7 @@
* Callback for receiving updates from the session. A Callback can be
* registered using {@link #registerCallback}.
*/
- public static abstract class Callback {
+ public abstract static class Callback {
/**
* Override to handle the session being destroyed. The session is no
* longer valid after this call and calls to it will be ignored.
@@ -1191,11 +1192,11 @@
}
}
- private final static class MessageHandler extends Handler {
+ private static final class MessageHandler extends Handler {
private final MediaController.Callback mCallback;
private boolean mRegistered = false;
- public MessageHandler(Looper looper, MediaController.Callback cb) {
+ MessageHandler(Looper looper, MediaController.Callback cb) {
super(looper);
mCallback = cb;
}
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
similarity index 99%
rename from media/java/android/media/session/MediaSessionEngine.java
rename to media/apex/java/android/media/session/MediaSessionEngine.java
index f159a95..1f5fa5f 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -53,7 +53,7 @@
*/
@SystemApi
public final class MediaSessionEngine implements AutoCloseable {
- private static final String TAG = MediaSession.TAG;
+ private static final String TAG = "MediaSession";
private final Object mLock = new Object();
private final int mMaxBitmapSize;
@@ -172,7 +172,7 @@
*
* @param flags The flags to set for this session.
*/
- public void setFlags(@MediaSession.SessionFlags int flags) {
+ public void setFlags(int flags) {
try {
mSessionLink.setFlags(flags);
} catch (RuntimeException e) {
@@ -409,7 +409,7 @@
* <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
* </ul>
*/
- public void setRatingType(@Rating.Style int type) {
+ public void setRatingType(int type) {
try {
mSessionLink.setRatingType(type);
} catch (RuntimeException e) {
diff --git a/media/java/android/media/session/MediaSessionProviderService.java b/media/apex/java/android/media/session/MediaSessionProviderService.java
similarity index 100%
rename from media/java/android/media/session/MediaSessionProviderService.java
rename to media/apex/java/android/media/session/MediaSessionProviderService.java
diff --git a/media/java/android/media/session/PlaybackState.aidl b/media/apex/java/android/media/session/PlaybackState.aidl
similarity index 100%
rename from media/java/android/media/session/PlaybackState.aidl
rename to media/apex/java/android/media/session/PlaybackState.aidl
diff --git a/media/java/android/media/session/PlaybackState.java b/media/apex/java/android/media/session/PlaybackState.java
similarity index 95%
rename from media/java/android/media/session/PlaybackState.java
rename to media/apex/java/android/media/session/PlaybackState.java
index 0d0ec4c..6b28c97 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/apex/java/android/media/session/PlaybackState.java
@@ -41,7 +41,7 @@
/**
* @hide
*/
- @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
+ @LongDef(flag = true, value = {ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
@@ -191,42 +191,42 @@
* @see Builder#setState(int, long, float)
* @see Builder#setState(int, long, float, long)
*/
- public final static int STATE_NONE = 0;
+ public static final int STATE_NONE = 0;
/**
* State indicating this item is currently stopped.
*
* @see Builder#setState
*/
- public final static int STATE_STOPPED = 1;
+ public static final int STATE_STOPPED = 1;
/**
* State indicating this item is currently paused.
*
* @see Builder#setState
*/
- public final static int STATE_PAUSED = 2;
+ public static final int STATE_PAUSED = 2;
/**
* State indicating this item is currently playing.
*
* @see Builder#setState
*/
- public final static int STATE_PLAYING = 3;
+ public static final int STATE_PLAYING = 3;
/**
* State indicating this item is currently fast forwarding.
*
* @see Builder#setState
*/
- public final static int STATE_FAST_FORWARDING = 4;
+ public static final int STATE_FAST_FORWARDING = 4;
/**
* State indicating this item is currently rewinding.
*
* @see Builder#setState
*/
- public final static int STATE_REWINDING = 5;
+ public static final int STATE_REWINDING = 5;
/**
* State indicating this item is currently buffering and will begin playing
@@ -234,7 +234,7 @@
*
* @see Builder#setState
*/
- public final static int STATE_BUFFERING = 6;
+ public static final int STATE_BUFFERING = 6;
/**
* State indicating this item is currently in an error state. The error
@@ -242,7 +242,7 @@
*
* @see Builder#setState
*/
- public final static int STATE_ERROR = 7;
+ public static final int STATE_ERROR = 7;
/**
* State indicating the class doing playback is currently connecting to a
@@ -252,21 +252,21 @@
*
* @see Builder#setState
*/
- public final static int STATE_CONNECTING = 8;
+ public static final int STATE_CONNECTING = 8;
/**
* State indicating the player is currently skipping to the previous item.
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+ public static final int STATE_SKIPPING_TO_PREVIOUS = 9;
/**
* State indicating the player is currently skipping to the next item.
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_NEXT = 10;
+ public static final int STATE_SKIPPING_TO_NEXT = 10;
/**
* State indicating the player is currently skipping to a specific item in
@@ -274,12 +274,12 @@
*
* @see Builder#setState
*/
- public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
+ public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
/**
* Use this value for the position to indicate the position is not known.
*/
- public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+ public static final long PLAYBACK_POSITION_UNKNOWN = -1;
private final int mState;
private final long mPosition;
@@ -534,19 +534,19 @@
return 0;
}
- public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR
- = new Parcelable.Creator<PlaybackState.CustomAction>() {
+ public static final Parcelable.Creator<PlaybackState.CustomAction> CREATOR =
+ new Parcelable.Creator<PlaybackState.CustomAction>() {
- @Override
- public PlaybackState.CustomAction createFromParcel(Parcel p) {
- return new PlaybackState.CustomAction(p);
- }
+ @Override
+ public PlaybackState.CustomAction createFromParcel(Parcel p) {
+ return new PlaybackState.CustomAction(p);
+ }
- @Override
- public PlaybackState.CustomAction[] newArray(int size) {
- return new PlaybackState.CustomAction[size];
- }
- };
+ @Override
+ public PlaybackState.CustomAction[] newArray(int size) {
+ return new PlaybackState.CustomAction[size];
+ }
+ };
/**
* Returns the action of the {@link CustomAction}.
@@ -588,10 +588,7 @@
@Override
public String toString() {
- return "Action:" +
- "mName='" + mName +
- ", mIcon=" + mIcon +
- ", mExtras=" + mExtras;
+ return "Action:" + "mName='" + mName + ", mIcon=" + mIcon + ", mExtras=" + mExtras;
}
/**
diff --git a/media/java/android/media/session/SessionCallbackLink.aidl b/media/apex/java/android/media/session/SessionCallbackLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.aidl
rename to media/apex/java/android/media/session/SessionCallbackLink.aidl
diff --git a/media/java/android/media/session/SessionCallbackLink.java b/media/apex/java/android/media/session/SessionCallbackLink.java
similarity index 100%
rename from media/java/android/media/session/SessionCallbackLink.java
rename to media/apex/java/android/media/session/SessionCallbackLink.java
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/apex/java/android/media/session/SessionLink.aidl
similarity index 100%
rename from media/java/android/media/session/SessionLink.aidl
rename to media/apex/java/android/media/session/SessionLink.aidl
diff --git a/media/java/android/media/session/SessionLink.java b/media/apex/java/android/media/session/SessionLink.java
similarity index 98%
rename from media/java/android/media/session/SessionLink.java
rename to media/apex/java/android/media/session/SessionLink.java
index 0da0a5a..4ea7623 100644
--- a/media/java/android/media/session/SessionLink.java
+++ b/media/apex/java/android/media/session/SessionLink.java
@@ -23,8 +23,6 @@
import android.media.AudioAttributes;
import android.media.MediaMetadata;
import android.media.MediaParceledListSlice;
-import android.media.Rating;
-import android.media.VolumeProvider;
import android.media.session.MediaSession.QueueItem;
import android.os.Bundle;
import android.os.IBinder;
@@ -234,7 +232,7 @@
*
* @param type the rating type.
*/
- void setRatingType(@Rating.Style int type) {
+ void setRatingType(int type) {
try {
mISession.setRatingType(type);
} catch (RemoteException e) {
@@ -261,7 +259,7 @@
* @param control the volume control type
* @param max the max volume
*/
- void setPlaybackToRemote(@VolumeProvider.ControlType int control, int max) {
+ void setPlaybackToRemote(int control, int max) {
try {
mISession.setPlaybackToRemote(control, max);
} catch (RemoteException e) {
diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/apex/java/android/service/media/IMediaBrowserService.aidl
similarity index 94%
rename from media/java/android/service/media/IMediaBrowserService.aidl
rename to media/apex/java/android/service/media/IMediaBrowserService.aidl
index 84f41f6..1c50ec7 100644
--- a/media/java/android/service/media/IMediaBrowserService.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserService.aidl
@@ -2,9 +2,7 @@
package android.service.media;
-import android.content.res.Configuration;
import android.service.media.IMediaBrowserServiceCallbacks;
-import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
similarity index 96%
rename from media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
rename to media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index 8dc480d..507a8f7 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,7 +2,6 @@
package android.service.media;
-import android.graphics.Bitmap;
import android.media.MediaParceledListSlice;
import android.media.session.MediaSession;
import android.os.Bundle;
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/apex/java/android/service/media/MediaBrowserService.java
similarity index 98%
rename from media/java/android/service/media/MediaBrowserService.java
rename to media/apex/java/android/service/media/MediaBrowserService.java
index 2fbc699..d9ef6ae 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/apex/java/android/service/media/MediaBrowserService.java
@@ -98,7 +98,7 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
+ @IntDef(flag = true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
private @interface ResultFlags { }
@@ -291,7 +291,7 @@
final ConnectionRecord connection = mConnections.get(b);
if (connection == null) {
Log.w(TAG, "addSubscription for callback that isn't registered id="
- + id);
+ + id);
return;
}
@@ -301,7 +301,8 @@
}
@Override
- public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
+ public void removeSubscriptionDeprecated(
+ String id, IMediaBrowserServiceCallbacks callbacks) {
// do-nothing
}
@@ -487,7 +488,7 @@
@Override
public void run() {
Iterator<ConnectionRecord> iter = mConnections.values().iterator();
- while (iter.hasNext()){
+ while (iter.hasNext()) {
ConnectionRecord connection = iter.next();
try {
connection.callbacks.onConnect(connection.root.getRootId(), token,
@@ -607,7 +608,7 @@
final PackageManager pm = getPackageManager();
final String[] packages = pm.getPackagesForUid(uid);
final int N = packages.length;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
if (packages[i].equals(pkg)) {
return true;
}
@@ -648,7 +649,7 @@
List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
if (callbackList != null) {
Iterator<Pair<IBinder, Bundle>> iter = callbackList.iterator();
- while (iter.hasNext()){
+ while (iter.hasNext()) {
if (token == iter.next().first) {
removed = true;
iter.remove();
@@ -819,8 +820,8 @@
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
- final private String mRootId;
- final private Bundle mExtras;
+ private final String mRootId;
+ private final Bundle mExtras;
/**
* Constructs a browser root.
@@ -829,8 +830,8 @@
*/
public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
if (rootId == null) {
- throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
- "Use null for BrowserRoot instead.");
+ throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. "
+ + "Use null for BrowserRoot instead.");
}
mRootId = rootId;
mExtras = extras;
diff --git a/media/packages/MediaCore/Android.bp b/media/packages/MediaCore/Android.bp.bak
similarity index 100%
rename from media/packages/MediaCore/Android.bp
rename to media/packages/MediaCore/Android.bp.bak
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 8b45af0..51afbc7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -231,6 +231,7 @@
ASurfaceTransaction_setBuffer; # introduced=29
ASurfaceTransaction_setBufferAlpha; # introduced=29
ASurfaceTransaction_setBufferTransparency; # introduced=29
+ ASurfaceTransaction_setColor; # introduced=29
ASurfaceTransaction_setDamageRegion; # introduced=29
ASurfaceTransaction_setDesiredPresentTime; # introduced=29
ASurfaceTransaction_setGeometry; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index f0100a9..5fae9d5 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -44,6 +44,76 @@
LOG_ALWAYS_FATAL_IF(!static_cast<const Rect&>(name).isValid(), \
"invalid arg passed as " #name " argument");
+static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
+ sp<SurfaceComposerClient> client = surfaceControl->getClient();
+ sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+ Vector<ui::ColorMode> colorModes;
+ status_t err = client->getDisplayColorModes(display, &colorModes);
+ if (err) {
+ ALOGE("unable to get wide color support");
+ return false;
+ }
+
+ bool wideColorBoardConfig =
+ getBool<ISurfaceFlingerConfigs,
+ &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
+
+ for (android::ui::ColorMode colorMode : colorModes) {
+ switch (colorMode) {
+ case ui::ColorMode::DISPLAY_P3:
+ case ui::ColorMode::ADOBE_RGB:
+ case ui::ColorMode::DCI_P3:
+ if (wideColorBoardConfig) {
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
+ sp<SurfaceComposerClient> client = surfaceControl->getClient();
+ sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
+
+ HdrCapabilities hdrCapabilities;
+ status_t err = client->getHdrCapabilities(display, &hdrCapabilities);
+ if (err) {
+ ALOGE("unable to get hdr capabilities");
+ return false;
+ }
+
+ return !hdrCapabilities.getSupportedHdrTypes().empty();
+}
+
+static bool isDataSpaceValid(const sp<SurfaceControl>& surfaceControl, ADataSpace dataSpace) {
+ static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
+ static_assert(static_cast<int>(ADATASPACE_SCRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SCRGB_LINEAR));
+ static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
+ static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
+ static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
+ static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+
+ switch (static_cast<android_dataspace_t>(dataSpace)) {
+ case HAL_DATASPACE_UNKNOWN:
+ case HAL_DATASPACE_V0_SRGB:
+ return true;
+ // These data space need wide gamut support.
+ case HAL_DATASPACE_V0_SCRGB_LINEAR:
+ case HAL_DATASPACE_V0_SCRGB:
+ case HAL_DATASPACE_DISPLAY_P3:
+ return getWideColorSupport(surfaceControl);
+ // These data space need HDR support.
+ case HAL_DATASPACE_BT2020_PQ:
+ return getHdrSupport(surfaceControl);
+ default:
+ return false;
+ }
+}
+
Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
return reinterpret_cast<Transaction*>(aSurfaceTransaction);
}
@@ -431,3 +501,24 @@
transaction->setHdrMetadata(surfaceControl, hdrMetadata);
}
+
+void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ float r, float g, float b, float alpha,
+ ADataSpace dataspace) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ LOG_ALWAYS_FATAL_IF(!isDataSpaceValid(surfaceControl, dataspace), "invalid dataspace");
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ half3 color;
+ color.r = r;
+ color.g = g;
+ color.b = b;
+
+ transaction->setColor(surfaceControl, color)
+ .setColorAlpha(surfaceControl, alpha)
+ .setColorDataspace(surfaceControl, static_cast<ui::Dataspace>(dataspace));
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 74aaf3c..93f6a94 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -47,6 +47,7 @@
import com.android.internal.widget.LockPatternUtils;
import java.util.List;
+import java.util.Set;
/**
* Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -325,7 +326,8 @@
if (admin == null) {
return null;
}
- if (dpm.getCrossProfileCalendarPackages().isEmpty()) {
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages();
+ if (packages != null && packages.isEmpty()) {
return admin;
}
return null;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 808739a..850a3c2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.providers.settings.GlobalSettingsProto;
import android.providers.settings.SecureSettingsProto;
import android.providers.settings.SettingProto;
@@ -397,7 +396,7 @@
p.end(certPinToken);
dumpSetting(s, p,
- Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+ Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
dumpSetting(s, p,
Settings.Global.COMPATIBILITY_MODE,
@@ -716,6 +715,9 @@
dumpSetting(s, p,
Settings.Global.GUP_BLACKLIST,
GlobalSettingsProto.Gpu.GUP_BLACKLIST);
+ dumpSetting(s, p,
+ Settings.Global.GAME_DRIVER_WHITELIST,
+ GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST);
p.end(gpuToken);
final long hdmiToken = p.start(GlobalSettingsProto.HDMI);
@@ -737,7 +739,7 @@
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
GlobalSettingsProto.HEADS_UP_NOTIFICATIONS_ENABLED);
dumpSetting(s, p,
- Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
+ Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
GlobalSettingsProto.HIDDEN_API_BLACKLIST_EXEMPTIONS);
final long inetCondToken = p.start(GlobalSettingsProto.INET_CONDITION);
@@ -832,6 +834,15 @@
dumpSetting(s, p,
Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
GlobalSettingsProto.LowPowerMode.AUTOMATIC_POWER_SAVER_MODE);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY,
+ GlobalSettingsProto.LowPowerMode.STICKY_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+ GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+ GlobalSettingsProto.LowPowerMode.STICKY_AUTO_DISABLE_LEVEL);
p.end(lpmToken);
dumpSetting(s, p,
@@ -882,7 +893,7 @@
p.end(multiSimToken);
dumpSetting(s, p,
- Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
+ Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
GlobalSettingsProto.NATIVE_FLAGS_HEALTH_CHECK_ENABLED);
final long netstatsToken = p.start(GlobalSettingsProto.NETSTATS);
@@ -1259,10 +1270,10 @@
final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER);
dumpSetting(s, p,
- Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+ Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
GlobalSettingsProto.SoundTrigger.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
dumpSetting(s, p,
- Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
+ Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
GlobalSettingsProto.SoundTrigger.DETECTION_SERVICE_OP_TIMEOUT_MS);
p.end(soundTriggerToken);
@@ -1558,7 +1569,7 @@
GlobalSettingsProto.ZRAM_ENABLED);
dumpSetting(s, p,
- Global.APP_OPS_CONSTANTS,
+ Settings.Global.APP_OPS_CONSTANTS,
GlobalSettingsProto.APP_OPS_CONSTANTS);
p.end(token);
@@ -2378,6 +2389,10 @@
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
SecureSettingsProto.THEME_CUSTOMIZATION_OVERLAY_PACKAGES);
+ dumpSetting(s, p,
+ Settings.Secure.AWARE_ENABLED,
+ SecureSettingsProto.AWARE_ENABLED);
+
// Please insert new settings using the same order as in SecureSettingsProto.
p.end(token);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 53cdee5..4d70890 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -47,6 +47,10 @@
public class AssistManager implements ConfigurationChangedReceiver {
private static final String TAG = "AssistManager";
+
+ // Note that VERBOSE logging may leak PII (e.g. transcription contents).
+ private static final boolean VERBOSE = false;
+
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
@@ -103,16 +107,41 @@
protected void registerVoiceInteractionSessionListener() {
mAssistUtils.registerVoiceInteractionSessionListener(
new IVoiceInteractionSessionListener.Stub() {
- @Override
- public void onVoiceSessionShown() throws RemoteException {
- Log.v(TAG, "Voice open");
- }
+ @Override
+ public void onVoiceSessionShown() throws RemoteException {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice open");
+ }
+ }
- @Override
- public void onVoiceSessionHidden() throws RemoteException {
- Log.v(TAG, "Voice closed");
- }
- });
+ @Override
+ public void onVoiceSessionHidden() throws RemoteException {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice closed");
+ }
+ }
+
+ @Override
+ public void onTranscriptionUpdate(String transcription) {
+ if (VERBOSE) {
+ Log.v(TAG, "Transcription Updated: \"" + transcription + "\"");
+ }
+ }
+
+ @Override
+ public void onTranscriptionComplete(boolean immediate) {
+ if (VERBOSE) {
+ Log.v(TAG, "Transcription complete (immediate=" + immediate + ")");
+ }
+ }
+
+ @Override
+ public void onVoiceStateChange(int state) {
+ if (VERBOSE) {
+ Log.v(TAG, "Voice state is now " + state);
+ }
+ }
+ });
}
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -291,8 +320,10 @@
}
}
} catch (PackageManager.NameNotFoundException e) {
- Log.v(TAG, "Assistant component "
- + component.flattenToShortString() + " not found");
+ if (VERBOSE) {
+ Log.v(TAG, "Assistant component "
+ + component.flattenToShortString() + " not found");
+ }
} catch (Resources.NotFoundException nfe) {
Log.w(TAG, "Failed to swap drawable from "
+ component.flattenToShortString(), nfe);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 7b6e79b..e8432b9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -33,6 +33,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.graphics.ColorUtils;
@@ -251,7 +252,7 @@
// HACK: release() will crash if the view is not attached.
mActivityView.setVisibility(View.GONE);
- tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ tmpParent.addView(mActivityView, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index bd1dfb1..5e9f207 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
import android.net.Uri;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -159,7 +160,8 @@
return bindGuts(row, mGutsMenuItem);
}
- private boolean bindGuts(final ExpandableNotificationRow row,
+ @VisibleForTesting
+ protected boolean bindGuts(final ExpandableNotificationRow row,
NotificationMenuRowPlugin.MenuItem item) {
StatusBarNotification sbn = row.getStatusBarNotification();
@@ -389,7 +391,11 @@
return false;
}
- mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+ LogMaker logMaker = (row.getStatusBarNotification() == null)
+ ? new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+ : row.getStatusBarNotification().getLogMaker();
+ mMetricsLogger.write(logMaker.setCategory(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION));
// ensure that it's laid but not visible until actually laid out
guts.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 49e6866..ee047e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -802,7 +802,9 @@
}
private void onAccessibilityClick(View v) {
- mAccessibilityManager.notifyAccessibilityButtonClicked();
+ final Display display = v.getDisplay();
+ mAccessibilityManager.notifyAccessibilityButtonClicked(
+ display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
}
private boolean onAccessibilityLongClick(View v) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 0899c73..40d2da9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -45,6 +45,7 @@
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.metrics.LogMaker;
import android.os.Binder;
import android.os.Handler;
import android.provider.Settings;
@@ -55,6 +56,8 @@
import android.util.ArraySet;
import android.view.View;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -92,6 +95,7 @@
private NotificationGutsManager mGutsManager;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock private MetricsLogger mMetricsLogger;
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private NotificationStackScrollLayout mStackScroller;
@@ -105,6 +109,7 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mHelper = new NotificationTestHelper(mContext);
@@ -141,7 +146,7 @@
when(row.getWindowToken()).thenReturn(new Binder());
when(row.getGuts()).thenReturn(guts);
- mGutsManager.openGuts(row, 0, 0, menuItem);
+ assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
assertEquals(View.INVISIBLE, guts.getVisibility());
mTestableLooper.processAllMessages();
verify(guts).openControls(
@@ -189,7 +194,7 @@
when(entry.getRow()).thenReturn(row);
when(entry.getGuts()).thenReturn(guts);
- mGutsManager.openGuts(row, 0, 0, menuItem);
+ assertTrue(mGutsManager.openGuts(row, 0, 0, menuItem));
mTestableLooper.processAllMessages();
verify(guts).openControls(
eq(true),
@@ -215,6 +220,34 @@
}
@Test
+ public void testOpenGutsLogging() {
+ NotificationGutsManager gutsManager = spy(mGutsManager);
+ doReturn(true).when(gutsManager).bindGuts(any(), any());
+
+ NotificationGuts guts = spy(new NotificationGuts(mContext));
+ doReturn(true).when(guts).post(any());
+
+ ExpandableNotificationRow realRow = createTestNotificationRow();
+ NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
+
+ ExpandableNotificationRow row = spy(realRow);
+ when(row.getWindowToken()).thenReturn(new Binder());
+ when(row.getGuts()).thenReturn(guts);
+ StatusBarNotification notification = spy(realRow.getStatusBarNotification());
+ when(row.getStatusBarNotification()).thenReturn(notification);
+
+ assertTrue(gutsManager.openGuts(row, 0, 0, menuItem));
+
+ ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(notification).getLogMaker();
+ verify(mMetricsLogger).write(logMakerCaptor.capture());
+ assertEquals(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS,
+ logMakerCaptor.getValue().getCategory());
+ assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION,
+ logMakerCaptor.getValue().getType());
+ }
+
+ @Test
public void testAppOpsSettingsIntent_camera() {
ArraySet<Integer> ops = new ArraySet<>();
ops.add(OP_CAMERA);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index efa4e79..d1aa84f 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6862,6 +6862,14 @@
// OS: Q
FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS = 1631;
+ // OPEN: Settings > System > Aware
+ // OS: Q
+ SETTINGS_AWARE = 1632;
+
+ // OPEN: Settings > System > Aware > Disable > Dialog
+ // OS: Q
+ DIALOG_AWARE_DISABLE = 1633;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f128d86..3a89316 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -241,6 +241,8 @@
NOTE_NETWORK_LOST_INTERNET = 742;
// The system default network switched to a different network
NOTE_NETWORK_SWITCH = 743;
+ // Device logged-in captive portal network successfully
+ NOTE_NETWORK_LOGGED_IN = 744;
// Notify the user that their work profile has been deleted
// Package: android
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 79b63bc..12f666e 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -491,6 +491,9 @@
// List of PNO scan stats, one element for each mobility state
repeated DeviceMobilityStatePnoScanStats mobility_state_pno_stats_list = 128;
+
+ // Wifi p2p statistics
+ optional WifiP2pStats wifi_p2p_stats = 129;
}
// Information that gets logged for every WiFi connection.
@@ -1800,6 +1803,17 @@
// Sequence number from external system app to framework
optional int32 seq_num_to_framework = 19;
+
+ // The total time CCA is on busy status on the current frequency in ms
+ // counted from the last radio chip reset
+ optional int64 total_cca_busy_freq_time_ms = 20;
+
+ // The total radio on time of the current frequency from the last radio
+ // chip reset
+ optional int64 total_radio_on_freq_time_ms = 21;
+
+ // The total number of beacons received from the last radio chip reset
+ optional int64 total_beacon_rx = 22;
}
message WifiUsabilityStats {
@@ -1849,3 +1863,135 @@
// the total duration elapsed while in this mobility state with PNO scans running, in ms
optional int64 pno_duration_ms = 4;
}
+
+// The information about the Wifi P2p events.
+message WifiP2pStats {
+
+ // Group event list tracking sessions and client counts in tethered mode.
+ repeated GroupEvent group_event = 1;
+
+ // Session information that gets logged for every Wifi P2p connection.
+ repeated P2pConnectionEvent connection_event = 2;
+
+ // Number of persistent group in the user profile.
+ optional int32 num_persistent_group = 3;
+
+ // Number of peer scan.
+ optional int32 num_total_peer_scans = 4;
+
+ // Number of service scan.
+ optional int32 num_total_service_scans = 5;
+}
+
+message P2pConnectionEvent {
+
+ enum ConnectionType {
+
+ // fresh new connection.
+ CONNECTION_FRESH = 0;
+
+ // reinvoke a group.
+ CONNECTION_REINVOKE = 1;
+
+ // create a group with the current device as the group owner locally.
+ CONNECTION_LOCAL = 2;
+
+ // create a group or join a group with config.
+ CONNECTION_FAST = 3;
+ }
+
+ enum ConnectivityLevelFailure {
+
+ // Failure is unknown.
+ CLF_UNKNOWN = 0;
+
+ // No failure.
+ CLF_NONE = 1;
+
+ // Timeout for current connecting request.
+ CLF_TIMEOUT = 2;
+
+ // The connecting request is canceled by the user.
+ CLF_CANCEL = 3;
+
+ // Provision discovery failure, e.g. no pin code, timeout, rejected by the peer.
+ CLF_PROV_DISC_FAIL = 4;
+
+ // Invitation failure, e.g. rejected by the peer.
+ CLF_INVITATION_FAIL = 5;
+
+ // Incoming request is rejected by the user.
+ CLF_USER_REJECT = 6;
+
+ // New connection request is issued before ending previous connecting request.
+ CLF_NEW_CONNECTION_ATTEMPT = 7;
+ }
+
+ // WPS method.
+ enum WpsMethod {
+ // WPS is skipped for Group Reinvoke.
+ WPS_NA = -1;
+
+ // Push button configuration.
+ WPS_PBC = 0;
+
+ // Display pin method configuration - pin is generated and displayed on device.
+ WPS_DISPLAY = 1;
+
+ // Keypad pin method configuration - pin is entered on device.
+ WPS_KEYPAD = 2;
+
+ // Label pin method configuration - pin is labelled on device.
+ WPS_LABEL = 3;
+ }
+
+ // Start time of the connection.
+ optional int64 start_time_millis = 1;
+
+ // Type of the connection.
+ optional ConnectionType connection_type = 2;
+
+ // WPS method.
+ optional WpsMethod wps_method = 3 [default = WPS_NA];
+
+ // Duration to connect.
+ optional int32 duration_taken_to_connect_millis = 4;
+
+ // Failures that happen at the connectivity layer.
+ optional ConnectivityLevelFailure connectivity_level_failure_code = 5;
+}
+
+// GroupEvent tracking group information from GroupStarted to GroupRemoved.
+message GroupEvent {
+
+ enum GroupRole {
+
+ GROUP_OWNER = 0;
+
+ GROUP_CLIENT = 1;
+ }
+
+ // The ID of network in supplicant for this group.
+ optional int32 net_id = 1;
+
+ // Start time of the group.
+ optional int64 start_time_millis = 2;
+
+ // Channel frequency used for Group.
+ optional int32 channel_frequency = 3;
+
+ // Is group owner or group client.
+ optional GroupRole group_role = 5;
+
+ // Number of connected clients.
+ optional int32 num_connected_clients = 6;
+
+ // Cumulative number of connected clients.
+ optional int32 num_cumulative_clients = 7;
+
+ // The session duration.
+ optional int32 session_duration_millis = 8;
+
+ // The idle duration. A group without any client is in idle.
+ optional int32 idle_duration_millis = 9;
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cf08681..bcff4e0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -17,13 +17,11 @@
package com.android.server.accessibility;
import android.content.Context;
-import android.os.Handler;
import android.os.PowerManager;
-import android.util.DebugUtils;
-import android.util.ExceptionUtils;
-import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
@@ -31,10 +29,11 @@
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
-import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
+import java.util.ArrayList;
+
/**
* This class is an input filter for implementing accessibility features such
* as display magnification and explore by touch.
@@ -108,23 +107,24 @@
private final AccessibilityManagerService mAms;
- private boolean mInstalled;
+ private final SparseArray<EventStreamTransformation> mEventHandler;
- private int mUserId;
+ private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
- private int mEnabledFeatures;
+ private final SparseArray<MagnificationGestureHandler> mMagnificationGestureHandler =
+ new SparseArray<>(0);
- private TouchExplorer mTouchExplorer;
-
- private MagnificationGestureHandler mMagnificationGestureHandler;
-
- private MotionEventInjector mMotionEventInjector;
+ private final SparseArray<MotionEventInjector> mMotionEventInjector = new SparseArray<>(0);
private AutoclickController mAutoclickController;
private KeyboardInterceptor mKeyboardInterceptor;
- private EventStreamTransformation mEventHandler;
+ private boolean mInstalled;
+
+ private int mUserId;
+
+ private int mEnabledFeatures;
private EventStreamState mMouseStreamState;
@@ -133,10 +133,16 @@
private EventStreamState mKeyboardStreamState;
AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
+ this(context, service, new SparseArray<>(0));
+ }
+
+ AccessibilityInputFilter(Context context, AccessibilityManagerService service,
+ SparseArray<EventStreamTransformation> eventHandler) {
super(context.getMainLooper());
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mEventHandler = eventHandler;
}
@Override
@@ -160,6 +166,13 @@
super.onUninstalled();
}
+ void onDisplayChanged() {
+ if (mInstalled) {
+ disableFeatures();
+ enableFeatures();
+ }
+ }
+
@Override
public void onInputEvent(InputEvent event, int policyFlags) {
if (DEBUG) {
@@ -167,8 +180,8 @@
+ Integer.toHexString(policyFlags));
}
- if (mEventHandler == null) {
- if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
+ if (mEventHandler.size() == 0) {
+ if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
super.onInputEvent(event, policyFlags);
return;
}
@@ -182,16 +195,16 @@
int eventSource = event.getSource();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
- mEventHandler.clearEvents(eventSource);
+ clearEventsForAllEventHandlers(eventSource);
super.onInputEvent(event, policyFlags);
return;
}
- if (state.updateDeviceId(event.getDeviceId())) {
- mEventHandler.clearEvents(eventSource);
+ if (state.updateInputSource(event.getSource())) {
+ clearEventsForAllEventHandlers(eventSource);
}
- if (!state.deviceIdValid()) {
+ if (!state.inputSourceValid()) {
super.onInputEvent(event, policyFlags);
return;
}
@@ -240,6 +253,15 @@
return null;
}
+ private void clearEventsForAllEventHandlers(int eventSource) {
+ for (int i = 0; i < mEventHandler.size(); i++) {
+ final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+ if (eventHandler != null) {
+ eventHandler.clearEvents(eventSource);
+ }
+ }
+ }
+
private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
super.onInputEvent(event, policyFlags);
@@ -258,7 +280,10 @@
super.onInputEvent(event, policyFlags);
return;
}
- mEventHandler.onKeyEvent(event, policyFlags);
+ // Since the display id of KeyEvent always would be -1 and there is only one
+ // KeyboardInterceptor for all display, pass KeyEvent to the mEventHandler of
+ // DEFAULT_DISPLAY to handle.
+ mEventHandler.get(Display.DEFAULT_DISPLAY).onKeyEvent(event, policyFlags);
}
private void handleMotionEvent(MotionEvent event, int policyFlags) {
@@ -267,10 +292,16 @@
}
mPm.userActivity(event.getEventTime(), false);
MotionEvent transformedEvent = MotionEvent.obtain(event);
- mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
+ final int displayId = event.getDisplayId();
+ mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
+ .onMotionEvent(transformedEvent, event, policyFlags);
transformedEvent.recycle();
}
+ private boolean isDisplayIdValid(int displayId) {
+ return mEventHandler.get(displayId) != null;
+ }
+
@Override
public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
int policyFlags) {
@@ -323,14 +354,20 @@
}
void notifyAccessibilityEvent(AccessibilityEvent event) {
- if (mEventHandler != null) {
- mEventHandler.onAccessibilityEvent(event);
+ for (int i = 0; i < mEventHandler.size(); i++) {
+ final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
+ if (eventHandler != null) {
+ eventHandler.onAccessibilityEvent(event);
+ }
}
}
- void notifyAccessibilityButtonClicked() {
- if (mMagnificationGestureHandler != null) {
- mMagnificationGestureHandler.notifyShortcutTriggered();
+ void notifyAccessibilityButtonClicked(int displayId) {
+ if (mMagnificationGestureHandler.size() != 0) {
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+ if (handler != null) {
+ handler.notifyShortcutTriggered();
+ }
}
}
@@ -339,81 +376,124 @@
resetStreamState();
+ final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
mAutoclickController = new AutoclickController(mContext, mUserId);
- addFirstEventHandler(mAutoclickController);
+ addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
}
- if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- mTouchExplorer = new TouchExplorer(mContext, mAms);
- addFirstEventHandler(mTouchExplorer);
- }
+ for (int i = displaysList.size() - 1; i >= 0; i--) {
+ final int displayId = displaysList.get(i).getDisplayId();
- if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
- || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
- final boolean detectControlGestures = (mEnabledFeatures
- & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
- final boolean triggerable = (mEnabledFeatures
- & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
- mMagnificationGestureHandler = new MagnificationGestureHandler(
- mContext, mAms.getMagnificationController(),
- detectControlGestures, triggerable);
- addFirstEventHandler(mMagnificationGestureHandler);
- }
+ if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ TouchExplorer explorer = new TouchExplorer(mContext, mAms);
+ addFirstEventHandler(displayId, explorer);
+ mTouchExplorer.put(displayId, explorer);
+ }
- if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
- mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
- addFirstEventHandler(mMotionEventInjector);
- mAms.setMotionEventInjector(mMotionEventInjector);
+ if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+ || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+ final boolean detectControlGestures = (mEnabledFeatures
+ & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
+ final boolean triggerable = (mEnabledFeatures
+ & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
+ MagnificationGestureHandler magnificationGestureHandler =
+ new MagnificationGestureHandler(mContext,
+ mAms.getMagnificationController(),
+ detectControlGestures, triggerable);
+ addFirstEventHandler(displayId, magnificationGestureHandler);
+ mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+ }
+
+ if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+ MotionEventInjector injector = new MotionEventInjector(
+ mContext.getMainLooper());
+ addFirstEventHandler(displayId, injector);
+ // TODO: Need to set MotionEventInjector per display.
+ mAms.setMotionEventInjector(injector);
+ mMotionEventInjector.put(displayId, injector);
+ }
}
if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
mKeyboardInterceptor = new KeyboardInterceptor(mAms,
LocalServices.getService(WindowManagerPolicy.class));
- addFirstEventHandler(mKeyboardInterceptor);
+ // Since the display id of KeyEvent always would be -1 and it would be dispatched to
+ // the display with input focus directly, we only need one KeyboardInterceptor for
+ // default display.
+ addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
}
}
/**
- * Adds an event handler to the event handler chain. The handler is added at the beginning of
- * the chain.
+ * Adds an event handler to the event handler chain for giving display. The handler is added at
+ * the beginning of the chain.
*
+ * @param displayId The logical display id.
* @param handler The handler to be added to the event handlers list.
*/
- private void addFirstEventHandler(EventStreamTransformation handler) {
- if (mEventHandler != null) {
- handler.setNext(mEventHandler);
+ private void addFirstEventHandler(int displayId, EventStreamTransformation handler) {
+ EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+ if (eventHandler != null) {
+ handler.setNext(eventHandler);
} else {
handler.setNext(this);
}
- mEventHandler = handler;
+ eventHandler = handler;
+ mEventHandler.put(displayId, eventHandler);
+ }
+
+ /**
+ * Adds an event handler to the event handler chain for all displays. The handler is added at
+ * the beginning of the chain.
+ *
+ * @param displayList The list of displays
+ * @param handler The handler to be added to the event handlers list.
+ */
+ private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
+ EventStreamTransformation handler) {
+ for (int i = 0; i < displayList.size(); i++) {
+ final int displayId = displayList.get(i).getDisplayId();
+ addFirstEventHandler(displayId, handler);
+ }
}
private void disableFeatures() {
- if (mMotionEventInjector != null) {
+ for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) {
+ final MotionEventInjector injector = mMotionEventInjector.valueAt(i);
+ // TODO: Need to set MotionEventInjector per display.
mAms.setMotionEventInjector(null);
- mMotionEventInjector.onDestroy();
- mMotionEventInjector = null;
+ if (injector != null) {
+ injector.onDestroy();
+ }
}
+ mMotionEventInjector.clear();
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
}
- if (mTouchExplorer != null) {
- mTouchExplorer.onDestroy();
- mTouchExplorer = null;
+ for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
+ final TouchExplorer explorer = mTouchExplorer.valueAt(i);
+ if (explorer != null) {
+ explorer.onDestroy();
+ }
}
- if (mMagnificationGestureHandler != null) {
- mMagnificationGestureHandler.onDestroy();
- mMagnificationGestureHandler = null;
+ mTouchExplorer.clear();
+ for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
+ if (handler != null) {
+ handler.onDestroy();
+ }
}
+ mMagnificationGestureHandler.clear();
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
- mEventHandler = null;
+ mEventHandler.clear();
resetStreamState();
}
@@ -441,41 +521,41 @@
* whose events should not be handled by a11y event stream transformations.
*/
private static class EventStreamState {
- private int mDeviceId;
+ private int mSource;
EventStreamState() {
- mDeviceId = -1;
+ mSource = -1;
}
/**
- * Updates the ID of the device associated with the state. If the ID changes, resets
- * internal state.
+ * Updates the input source of the device associated with the state. If the source changes,
+ * resets internal state.
*
- * @param deviceId Updated input device ID.
- * @return Whether the device ID has changed.
+ * @param source Updated input source.
+ * @return Whether the input source has changed.
*/
- public boolean updateDeviceId(int deviceId) {
- if (mDeviceId == deviceId) {
+ public boolean updateInputSource(int source) {
+ if (mSource == source) {
return false;
}
- // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
+ // Reset clears internal state, so make sure it's called before |mSource| is updated.
reset();
- mDeviceId = deviceId;
+ mSource = source;
return true;
}
/**
- * @return Whether device ID is valid.
+ * @return Whether input source is valid.
*/
- public boolean deviceIdValid() {
- return mDeviceId >= 0;
+ public boolean inputSourceValid() {
+ return mSource >= 0;
}
/**
* Resets the event stream state.
*/
public void reset() {
- mDeviceId = -1;
+ mSource = -1;
}
/**
@@ -592,20 +672,19 @@
/*
* Key events from different devices may be interleaved. For example, the volume up and
- * down keys can come from different device IDs.
+ * down keys can come from different input sources.
*/
@Override
- public boolean updateDeviceId(int deviceId) {
+ public boolean updateInputSource(int deviceId) {
return false;
}
- // We manage all device ids simultaneously; there is no concept of validity.
+ // We manage all input source simultaneously; there is no concept of validity.
@Override
- public boolean deviceIdValid() {
+ public boolean inputSourceValid() {
return true;
}
-
@Override
final public boolean shouldProcessKeyEvent(KeyEvent event) {
// For each keyboard device, wait for a down event from a device to start processing
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index dbe86c1..305c53e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -38,6 +38,7 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -208,12 +209,12 @@
private final WindowManagerInternal mWindowManagerService;
- private final DisplayManager mDisplayManager;
-
private AppWidgetManagerInternal mAppWidgetService;
private final SecurityPolicy mSecurityPolicy;
+ private final AccessibilityDisplayListener mA11yDisplayListener;
+
private final AppOpsManager mAppOpsManager;
private final MainHandler mMainHandler;
@@ -306,12 +307,11 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mMainHandler = new MainHandler(mContext.getMainLooper());
mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
context.getContentResolver());
- registerDisplayListener(mMainHandler);
}
@Override
@@ -527,30 +527,6 @@
}, UserHandle.ALL, intentFilter, null, null);
}
- private void registerDisplayListener(Handler handler) {
- mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- updateMagnificationLocked(userState);
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- if (mMagnificationController != null) {
- mMagnificationController.onDisplayRemoved(displayId);
- }
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- // do nothing
- }
- }, handler);
- }
-
@Override
public long addClient(IAccessibilityManagerClient callback, int userId) {
synchronized (mLock) {
@@ -937,16 +913,18 @@
/**
* Invoked remotely over AIDL by SysUi when the accessibility button within the system's
* navigation area has been clicked.
+ *
+ * @param displayId The logical display id.
*/
@Override
- public void notifyAccessibilityButtonClicked() {
+ public void notifyAccessibilityButtonClicked(int displayId) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
synchronized (mLock) {
- notifyAccessibilityButtonClickedLocked();
+ notifyAccessibilityButtonClickedLocked(displayId);
}
}
@@ -1249,7 +1227,7 @@
}
}
- private void notifyAccessibilityButtonClickedLocked() {
+ private void notifyAccessibilityButtonClickedLocked(int displayId) {
final UserState state = getCurrentUserStateLocked();
int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
@@ -1266,12 +1244,15 @@
if (potentialTargets == 1) {
if (state.mIsNavBarMagnificationEnabled) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+ AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+ displayId));
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
+ // TODO(b/120762691): Need to notify each accessibility service if
+ // accessibility button is clicked per display.
service.notifyAccessibilityButtonClickedLocked();
return;
}
@@ -1281,17 +1262,21 @@
if (state.mServiceAssignedToAccessibilityButton == null
&& !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+ AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+ displayId));
} else if (state.mIsNavBarMagnificationEnabled
&& state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this));
+ AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
+ displayId));
return;
} else {
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
state.mServiceAssignedToAccessibilityButton))) {
+ // TODO(b/120762691): Need to notify each accessibility service if
+ // accessibility button is clicked per display.
service.notifyAccessibilityButtonClickedLocked();
return;
}
@@ -1299,22 +1284,24 @@
}
// The user may have turned off the assigned service or feature
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this));
+ AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
+ displayId));
}
}
- private void sendAccessibilityButtonToInputFilter() {
+ private void sendAccessibilityButtonToInputFilter(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
- mInputFilter.notifyAccessibilityButtonClicked();
+ mInputFilter.notifyAccessibilityButtonClicked(displayId);
}
}
}
- private void showAccessibilityButtonTargetSelection() {
+ private void showAccessibilityButtonTargetSelection(int displayId) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivityAsUser(intent, UserHandle.of(mCurrentUserId));
+ final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+ mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
}
private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
@@ -2226,32 +2213,22 @@
return;
}
- // register all display if global magnification is enabled.
- final Display[] displays = mDisplayManager.getDisplays();
+ // Get all valid displays and register them if global magnification is enabled.
+ // We would skip overlay display because it uses overlay window to simulate secondary
+ // displays in one display. It's not a real display and there's no input events for it.
+ final ArrayList<Display> displays = getValidDisplayList();
if (userState.mIsDisplayMagnificationEnabled
|| userState.mIsNavBarMagnificationEnabled) {
- for (int i = 0; i < displays.length; i++) {
- final Display display = displays[i];
- // Overlay display uses overlay window to simulate secondary displays in
- // one display. It's not a real display and there's no input events for it.
- // We should ignore it.
- if (display.getType() == Display.TYPE_OVERLAY) {
- continue;
- }
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
}
return;
}
- // register if display has listening magnification services.
- for (int i = 0; i < displays.length; i++) {
- final Display display = displays[i];
- // Overlay display uses overlay window to simulate secondary displays in
- // one display. It's not a real display and there's no input events for it.
- // We should ignore it.
- if (display.getType() == Display.TYPE_OVERLAY) {
- continue;
- }
+ // Register if display has listening magnification services.
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
final int displayId = display.getDisplayId();
if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
getMagnificationController().register(displayId);
@@ -3812,6 +3789,92 @@
}
}
+ /**
+ * Gets all currently valid logical displays.
+ *
+ * @return An array list containing all valid logical displays.
+ */
+ public ArrayList<Display> getValidDisplayList() {
+ return mA11yDisplayListener.getValidDisplayList();
+ }
+
+ /**
+ * A Utility class to handle display state.
+ */
+ public class AccessibilityDisplayListener implements DisplayManager.DisplayListener {
+ private final DisplayManager mDisplayManager;
+ private final ArrayList<Display> mDisplaysList = new ArrayList<>();
+
+ AccessibilityDisplayListener(Context context, MainHandler handler) {
+ mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(this, handler);
+ initializeDisplayList();
+ }
+
+ ArrayList<Display> getValidDisplayList() {
+ synchronized (mLock) {
+ return mDisplaysList;
+ }
+ }
+
+ private void initializeDisplayList() {
+ final Display[] displays = mDisplayManager.getDisplays();
+ synchronized (mLock) {
+ mDisplaysList.clear();
+ for (int i = 0; i < displays.length; i++) {
+ // Exclude overlay virtual displays. The display list is for A11yInputFilter
+ // to create event handler per display. The events should be handled by the
+ // display which is overlaid by it.
+ final Display display = displays[i];
+ if (display.getType() == Display.TYPE_OVERLAY) {
+ continue;
+ }
+ mDisplaysList.add(display);
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ final Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null || display.getType() == Display.TYPE_OVERLAY) {
+ return;
+ }
+
+ synchronized (mLock) {
+ mDisplaysList.add(display);
+ if (mInputFilter != null) {
+ mInputFilter.onDisplayChanged();
+ }
+ UserState userState = getCurrentUserStateLocked();
+ updateMagnificationLocked(userState);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplaysList.size(); i++) {
+ if (mDisplaysList.get(i).getDisplayId() == displayId) {
+ mDisplaysList.remove(i);
+ break;
+ }
+ }
+ if (mInputFilter != null) {
+ mInputFilter.onDisplayChanged();
+ }
+ }
+ if (mMagnificationController != null) {
+ mMagnificationController.onDisplayRemoved(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ /* do nothing */
+ }
+ }
+
/** Represents an {@link AccessibilityManager} */
class Client {
final IAccessibilityManagerClient mCallback;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index e4bbcd6..844096d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -35,6 +35,7 @@
import android.util.LocalLog;
import android.util.Slog;
import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -224,6 +225,15 @@
}
@Override
+ public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
+ Preconditions.checkNotNull(request);
+ synchronized (mLock) {
+ final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ service.removeUserDataLocked(request);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 8d2c79b..bc0e19a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -28,6 +28,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -35,18 +36,22 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
import com.android.server.contentcapture.RemoteContentCaptureService.ContentCaptureServiceCallbacks;
import com.android.server.infra.AbstractPerUserSystemService;
@@ -249,6 +254,39 @@
}
@GuardedBy("mLock")
+ public void removeUserDataLocked(@NonNull UserDataRemovalRequest request) {
+ if (!isEnabledLocked()) {
+ return;
+ }
+ assertCallerLocked(request.getPackageName());
+ mRemoteService.onUserDataRemovalRequest(request);
+ }
+
+ /**
+ * Asserts the component is owned by the caller.
+ */
+ @GuardedBy("mLock")
+ private void assertCallerLocked(@NonNull String packageName) {
+ final PackageManager pm = getContext().getPackageManager();
+ final int callingUid = Binder.getCallingUid();
+ final int packageUid;
+ try {
+ packageUid = pm.getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+ } catch (NameNotFoundException e) {
+ throw new SecurityException("Could not verify UID for " + packageName);
+ }
+ if (callingUid != packageUid && !LocalServices.getService(ActivityManagerInternal.class)
+ .hasRunningActivity(callingUid, packageName)) {
+ final String[] packages = pm.getPackagesForUid(callingUid);
+ final String callingPackage = packages != null ? packages[0] : "uid-" + callingUid;
+ Slog.w(TAG, "App (package=" + callingPackage + ", UID=" + callingUid
+ + ") passed package (" + packageName + ") owned by UID " + packageUid);
+
+ throw new SecurityException("Invalid package: " + packageName);
+ }
+ }
+
+ @GuardedBy("mLock")
public boolean sendActivityAssistDataLocked(@NonNull IBinder activityToken,
@NonNull Bundle data) {
final String id = getSessionId(activityToken);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 12742ca..54eea5d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -26,6 +26,7 @@
import android.text.format.DateUtils;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
+import android.view.contentcapture.UserDataRemovalRequest;
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
import com.android.internal.os.IResultReceiver;
@@ -108,6 +109,13 @@
scheduleAsyncRequest((s) -> s.onActivitySnapshot(sessionId, snapshotData));
}
+ /**
+ * Called by {@link ContentCaptureServerSession} to request removal of user data.
+ */
+ public void onUserDataRemovalRequest(@NonNull UserDataRemovalRequest request) {
+ scheduleAsyncRequest((s) -> s.onUserDataRemovalRequest(request));
+ }
+
public interface ContentCaptureServiceCallbacks
extends VultureCallback<RemoteContentCaptureService> {
// NOTE: so far we don't need to notify the callback implementation
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index fcd136c..e3dcb7d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -77,6 +77,7 @@
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
+import android.util.LongArrayQueue;
import android.util.NtpTrustedTime;
import android.util.Pair;
import android.util.Slog;
@@ -91,6 +92,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.LocalLog;
import com.android.internal.util.StatLogger;
import com.android.server.AppStateTracker.Listener;
@@ -145,6 +147,7 @@
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
static final int TICK_HISTORY_DEPTH = 10;
+ static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
// Indices into the APP_STANDBY_MIN_DELAYS and KEYS_APP_STANDBY_DELAY arrays
static final int ACTIVE_INDEX = 0;
@@ -195,6 +198,7 @@
ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
ArrayList<InFlight> mInFlight = new ArrayList<>();
AlarmHandler mHandler;
+ AppWakeupHistory mAppWakeupHistory;
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
Intent mTimeTickIntent;
@@ -277,7 +281,91 @@
private AppStateTracker mAppStateTracker;
private boolean mAppStandbyParole;
- private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
+
+ /**
+ * A rolling window history of previous times when an alarm was sent to a package.
+ */
+ private static class AppWakeupHistory {
+ private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
+ new ArrayMap<>();
+ private long mWindowSize;
+
+ AppWakeupHistory(long windowSize) {
+ mWindowSize = windowSize;
+ }
+
+ void recordAlarmForPackage(String packageName, int userId, long nowElapsed) {
+ final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+ LongArrayQueue history = mPackageHistory.get(packageUser);
+ if (history == null) {
+ history = new LongArrayQueue();
+ mPackageHistory.put(packageUser, history);
+ }
+ if (history.size() == 0 || history.peekLast() < nowElapsed) {
+ history.addLast(nowElapsed);
+ }
+ snapToWindow(history);
+ }
+
+ void removeForUser(int userId) {
+ for (int i = mPackageHistory.size() - 1; i >= 0; i--) {
+ final Pair<String, Integer> packageUserKey = mPackageHistory.keyAt(i);
+ if (packageUserKey.second == userId) {
+ mPackageHistory.removeAt(i);
+ }
+ }
+ }
+
+ void removeForPackage(String packageName, int userId) {
+ final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
+ mPackageHistory.remove(packageUser);
+ }
+
+ private void snapToWindow(LongArrayQueue history) {
+ while (history.peekFirst() + mWindowSize < history.peekLast()) {
+ history.removeFirst();
+ }
+ }
+
+ int getTotalWakeupsInWindow(String packageName, int userId) {
+ final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ return (history == null) ? 0 : history.size();
+ }
+
+ long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) {
+ final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId));
+ if (history == null) {
+ return 0;
+ }
+ final int i = history.size() - positionFromEnd;
+ return (i < 0) ? 0 : history.get(i);
+ }
+
+ void dump(PrintWriter pw, String prefix, long nowElapsed) {
+ dump(new IndentingPrintWriter(pw, " ").setIndent(prefix), nowElapsed);
+ }
+
+ void dump(IndentingPrintWriter pw, long nowElapsed) {
+ pw.println("App Alarm history:");
+ pw.increaseIndent();
+ for (int i = 0; i < mPackageHistory.size(); i++) {
+ final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
+ final LongArrayQueue timestamps = mPackageHistory.valueAt(i);
+ pw.print(packageUser.first);
+ pw.print(", u");
+ pw.print(packageUser.second);
+ pw.print(": ");
+ // limit dumping to a max of 100 values
+ final int lastIdx = Math.max(0, timestamps.size() - 100);
+ for (int j = timestamps.size() - 1; j >= lastIdx; j--) {
+ TimeUtils.formatDuration(timestamps.get(j), nowElapsed, pw);
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+ }
/**
* All times are in milliseconds. These constants are kept synchronized with the system
@@ -302,6 +390,17 @@
= "allow_while_idle_whitelist_duration";
@VisibleForTesting
static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
+ @VisibleForTesting
+ static final String KEY_APP_STANDBY_QUOTAS_ENABLED = "app_standby_quotas_enabled";
+ private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window";
+ @VisibleForTesting
+ final String[] KEYS_APP_STANDBY_QUOTAS = {
+ "standby_active_quota",
+ "standby_working_quota",
+ "standby_frequent_quota",
+ "standby_rare_quota",
+ "standby_never_quota",
+ };
// Keys for specifying throttling delay based on app standby bucketing
private final String[] KEYS_APP_STANDBY_DELAY = {
@@ -319,6 +418,18 @@
private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+ private static final boolean DEFAULT_APP_STANDBY_QUOTAS_ENABLED = true;
+ private static final long DEFAULT_APP_STANDBY_WINDOW = 60 * 60 * 1000; // 1 hr
+ /**
+ * Max number of times an app can receive alarms in {@link #APP_STANDBY_WINDOW}
+ */
+ private final int[] DEFAULT_APP_STANDBY_QUOTAS = {
+ 720, // Active
+ 10, // Working
+ 2, // Frequent
+ 1, // Rare
+ 0 // Never
+ };
private final long[] DEFAULT_APP_STANDBY_DELAYS = {
0, // Active
6 * 60_000, // Working
@@ -348,8 +459,11 @@
// Direct alarm listener callback timeout
public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+ public boolean APP_STANDBY_QUOTAS_ENABLED = DEFAULT_APP_STANDBY_QUOTAS_ENABLED;
+ public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length];
+ public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length];
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -409,48 +523,90 @@
DEFAULT_APP_STANDBY_DELAYS[ACTIVE_INDEX]);
for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_DELAY.length; i++) {
APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i],
- Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i]));
+ Math.max(APP_STANDBY_MIN_DELAYS[i - 1], DEFAULT_APP_STANDBY_DELAYS[i]));
+ }
+
+ APP_STANDBY_QUOTAS_ENABLED = mParser.getBoolean(KEY_APP_STANDBY_QUOTAS_ENABLED,
+ DEFAULT_APP_STANDBY_QUOTAS_ENABLED);
+
+ APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW,
+ DEFAULT_APP_STANDBY_WINDOW);
+ if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
+ Slog.w(TAG, "Cannot exceed the app_standby_window size of "
+ + DEFAULT_APP_STANDBY_WINDOW);
+ APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
+ } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
+ // Not recommended outside of testing.
+ Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
+ }
+
+ APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt(
+ KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
+ DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+ APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i],
+ Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
}
updateAllowWhileIdleWhitelistDurationLocked();
}
}
- void dump(PrintWriter pw) {
- pw.println(" Settings:");
+ void dump(PrintWriter pw, String prefix) {
+ dump(new IndentingPrintWriter(pw, " ").setIndent(prefix));
+ }
- pw.print(" "); pw.print(KEY_MIN_FUTURITY); pw.print("=");
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Settings:");
+
+ pw.increaseIndent();
+
+ pw.print(KEY_MIN_FUTURITY); pw.print("=");
TimeUtils.formatDuration(MIN_FUTURITY, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MIN_INTERVAL); pw.print("=");
+ pw.print(KEY_MIN_INTERVAL); pw.print("=");
TimeUtils.formatDuration(MIN_INTERVAL, pw);
pw.println();
- pw.print(" "); pw.print(KEY_MAX_INTERVAL); pw.print("=");
+ pw.print(KEY_MAX_INTERVAL); pw.print("=");
TimeUtils.formatDuration(MAX_INTERVAL, pw);
pw.println();
- pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+ pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
pw.println();
- pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
+ pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
pw.println();
for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) {
- pw.print(" "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
+ pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("=");
TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw);
pw.println();
}
+
+ pw.print(KEY_APP_STANDBY_QUOTAS_ENABLED); pw.print("=");
+ pw.println(APP_STANDBY_QUOTAS_ENABLED);
+
+ pw.print(KEY_APP_STANDBY_WINDOW); pw.print("=");
+ TimeUtils.formatDuration(APP_STANDBY_WINDOW, pw);
+ pw.println();
+
+ for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+ pw.print(KEYS_APP_STANDBY_QUOTAS[i]); pw.print("=");
+ pw.println(APP_STANDBY_QUOTAS[i]);
+ }
+
+ pw.decreaseIndent();
}
void dumpProto(ProtoOutputStream proto, long fieldId) {
@@ -925,7 +1081,7 @@
if (targetPackages != null && !targetPackages.contains(packageUser)) {
continue;
}
- if (adjustDeliveryTimeBasedOnStandbyBucketLocked(alarm)) {
+ if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
batch.remove(alarm);
rescheduledAlarms.add(alarm);
}
@@ -1300,6 +1456,7 @@
synchronized (mLock) {
mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
+ mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1583,6 +1740,27 @@
}
/**
+ * Returns the maximum alarms that an app in the specified bucket can receive in a rolling time
+ * window given by {@link Constants#APP_STANDBY_WINDOW}
+ */
+ @VisibleForTesting
+ int getQuotaForBucketLocked(int bucket) {
+ final int index;
+ if (bucket <= UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
+ index = ACTIVE_INDEX;
+ } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
+ index = WORKING_INDEX;
+ } else if (bucket <= UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+ index = FREQUENT_INDEX;
+ } else if (bucket < UsageStatsManager.STANDBY_BUCKET_NEVER) {
+ index = RARE_INDEX;
+ } else {
+ index = NEVER_INDEX;
+ }
+ return mConstants.APP_STANDBY_QUOTAS[index];
+ }
+
+ /**
* Return the minimum time that should elapse before an app in the specified bucket
* can receive alarms again
*/
@@ -1608,7 +1786,7 @@
* @param alarm The alarm to adjust
* @return true if the alarm delivery time was updated.
*/
- private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
+ private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
if (isExemptFromAppStandby(alarm)) {
return false;
}
@@ -1629,18 +1807,49 @@
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
- final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
- final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);
- if (lastElapsed > 0) {
- final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
- if (alarm.expectedWhenElapsed < minElapsed) {
- alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
- } else {
- // app is now eligible to run alarms at the originally requested window.
+ if (mConstants.APP_STANDBY_QUOTAS_ENABLED) {
+ // Quota deferring implementation:
+ final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
+ sourceUserId);
+ final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
+ boolean deferred = false;
+ if (wakeupsInWindow >= quotaForBucket) {
+ final long minElapsed;
+ if (quotaForBucket <= 0) {
+ // Just keep deferring for a day till the quota changes
+ minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+ } else {
+ // Suppose the quota for window was q, and the qth last delivery time for this
+ // package was t(q) then the next delivery must be after t(q) + <window_size>
+ final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+ sourceUserId, quotaForBucket);
+ minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW;
+ }
+ if (alarm.expectedWhenElapsed < minElapsed) {
+ alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ deferred = true;
+ }
+ }
+ if (!deferred) {
// Restore original requirements in case they were changed earlier.
alarm.whenElapsed = alarm.expectedWhenElapsed;
alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
+ } else {
+ // Minimum delay deferring implementation:
+ final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
+ sourceUserId, 1);
+ if (lastElapsed > 0) {
+ final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
+ if (alarm.expectedWhenElapsed < minElapsed) {
+ alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
+ } else {
+ // app is now eligible to run alarms at the originally requested window.
+ // Restore original requirements in case they were changed earlier.
+ alarm.whenElapsed = alarm.expectedWhenElapsed;
+ alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
+ }
+ }
}
return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
}
@@ -1696,7 +1905,7 @@
mAllowWhileIdleDispatches.add(ent);
}
}
- adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
+ adjustDeliveryTimeBasedOnBucketLocked(a);
insertAndBatchAlarmLocked(a);
if (a.alarmClock != null) {
@@ -1915,7 +2124,7 @@
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
- mConstants.dump(pw);
+ mConstants.dump(pw, " ");
pw.println();
if (mAppStateTracker != null) {
@@ -2065,14 +2274,7 @@
pw.println(" none");
}
- pw.println(" mLastAlarmDeliveredForPackage:");
- for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) {
- Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
- pw.print(" Package " + packageUser.first + ", User " + packageUser.second + ":");
- TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw);
- pw.println();
- }
- pw.println();
+ mAppWakeupHistory.dump(pw, " ", nowELAPSED);
if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
pw.println();
@@ -3862,6 +4064,7 @@
obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
}
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ALARM_EVENT: {
@@ -4030,64 +4233,57 @@
public void onReceive(Context context, Intent intent) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
- String action = intent.getAction();
String pkgList[] = null;
- if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- for (String packageName : pkgList) {
- if (lookForPackageLocked(packageName)) {
- setResultCode(Activity.RESULT_OK);
- return;
- }
- }
- return;
- } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
- } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userHandle >= 0) {
- removeUserLocked(userHandle);
- for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
- final Pair<String, Integer> packageUser =
- mLastAlarmDeliveredForPackage.keyAt(i);
- if (packageUser.second == userHandle) {
- mLastAlarmDeliveredForPackage.removeAt(i);
+ switch (intent.getAction()) {
+ case Intent.ACTION_QUERY_PACKAGE_RESTART:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ for (String packageName : pkgList) {
+ if (lookForPackageLocked(packageName)) {
+ setResultCode(Activity.RESULT_OK);
+ return;
}
}
- }
- } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
- if (uid >= 0) {
- mLastAllowWhileIdleDispatch.delete(uid);
- mUseAllowWhileIdleShortTime.delete(uid);
- }
- } else {
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
- && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- // This package is being updated; don't kill its alarms.
return;
- }
- Uri data = intent.getData();
- if (data != null) {
- String pkg = data.getSchemeSpecificPart();
- if (pkg != null) {
- pkgList = new String[]{pkg};
+ case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+ pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ break;
+ case Intent.ACTION_USER_STOPPED:
+ final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userHandle >= 0) {
+ removeUserLocked(userHandle);
+ mAppWakeupHistory.removeForUser(userHandle);
}
- }
+ return;
+ case Intent.ACTION_UID_REMOVED:
+ if (uid >= 0) {
+ mLastAllowWhileIdleDispatch.delete(uid);
+ mUseAllowWhileIdleShortTime.delete(uid);
+ }
+ return;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // This package is being updated; don't kill its alarms.
+ return;
+ }
+ // Intentional fall-through.
+ case Intent.ACTION_PACKAGE_RESTARTED:
+ final Uri data = intent.getData();
+ if (data != null) {
+ final String pkg = data.getSchemeSpecificPart();
+ if (pkg != null) {
+ pkgList = new String[]{pkg};
+ }
+ }
+ break;
}
if (pkgList != null && (pkgList.length > 0)) {
- for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) {
- Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i);
- if (ArrayUtils.contains(pkgList, packageUser.first)
- && packageUser.second == UserHandle.getUserId(uid)) {
- mLastAlarmDeliveredForPackage.removeAt(i);
- }
- }
for (String pkg : pkgList) {
if (uid >= 0) {
- // package-removed case
+ // package-removed and package-restarted case
+ mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
removeLocked(uid);
} else {
- // external-applications-unavailable etc case
+ // external-applications-unavailable case
removeLocked(pkg);
}
mPriorities.remove(pkg);
@@ -4131,7 +4327,8 @@
/**
* Tracking of app assignments to standby buckets
*/
- final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener {
+ private final class AppStandbyTracker extends
+ UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket, int reason) {
@@ -4474,7 +4671,8 @@
if (!isExemptFromAppStandby(alarm)) {
final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid));
- mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED);
+ mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
+ UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
}
final BroadcastStats bs = inflight.mBroadcastStats;
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index a333381..c4bc52c 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,6 +208,8 @@
private int mErrorRecoveryRetryCounter;
private final int mSystemUiUid;
+ private boolean mIsHearingAidProfileSupported;
+
// Save a ProfileServiceConnections object for each of the bound
// bluetooth profile services
private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
@@ -391,13 +393,19 @@
mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+ mIsHearingAidProfileSupported = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
// TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
- boolean isHearingAidEnabled;
String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
if (!TextUtils.isEmpty(value)) {
- isHearingAidEnabled = Boolean.parseBoolean(value);
+ boolean isHearingAidEnabled = Boolean.parseBoolean(value);
Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+ if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+ // Overwrite to enable support by FeatureFlag
+ mIsHearingAidProfileSupported = true;
+ }
}
IntentFilter filter = new IntentFilter();
@@ -679,6 +687,11 @@
return false;
}
+ @Override
+ public boolean isHearingAidProfileSupported() {
+ return mIsHearingAidProfileSupported;
+ }
+
// Monitor change of BLE scan only mode settings.
private void registerForBleScanModeChange() {
ContentObserver contentObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index aa74600..14e2354 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -73,6 +73,7 @@
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
+import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
@@ -97,10 +98,10 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetdService;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
-import android.net.shared.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -237,6 +238,9 @@
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to dismiss network notification.
+ private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -473,6 +477,11 @@
public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
/**
+ * This event can handle dismissing notification by given network id.
+ */
+ public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -506,7 +515,8 @@
// A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
// the world when it changes.
- private final ProxyTracker mProxyTracker;
+ @VisibleForTesting
+ protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
@@ -815,7 +825,7 @@
mPolicyManagerInternal = checkNotNull(
LocalServices.getService(NetworkPolicyManagerInternal.class),
"missing NetworkPolicyManagerInternal");
- mProxyTracker = new ProxyTracker(context, mHandler, EVENT_PROXY_HAS_CHANGED);
+ mProxyTracker = makeProxyTracker();
mNetd = NetdService.getInstance();
mKeyStore = KeyStore.getInstance();
@@ -981,6 +991,11 @@
deps);
}
+ @VisibleForTesting
+ protected ProxyTracker makeProxyTracker() {
+ return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+ }
+
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -2476,6 +2491,11 @@
final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
+ if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
+ && valid) {
+ nai.captivePortalLoginNotified = true;
+ showNetworkNotification(nai, NotificationType.LOGGED_IN);
+ }
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
@@ -2496,7 +2516,15 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
- if (valid) handleFreshlyValidatedNetwork(nai);
+ if (valid) {
+ handleFreshlyValidatedNetwork(nai);
+ // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
+ // valid.
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.NO_INTERNET);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.LOST_INTERNET);
+ }
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
@@ -2520,6 +2548,9 @@
final int oldScore = nai.getCurrentScore();
nai.lastCaptivePortalDetected = visible;
nai.everCaptivePortalDetected |= visible;
+ if (visible) {
+ nai.captivePortalLoginNotified = false;
+ }
if (nai.lastCaptivePortalDetected &&
Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -2531,7 +2562,10 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
- mNotifier.clearNotification(netId);
+ // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+ // notifications belong to the same network may be cleared unexpected.
+ mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+ mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else {
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -3237,9 +3271,15 @@
pw.decreaseIndent();
}
- private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+ private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action;
switch (type) {
+ case LOGGED_IN:
+ action = Settings.ACTION_WIFI_SETTINGS;
+ mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+ nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+ break;
case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
break;
@@ -3252,10 +3292,12 @@
}
Intent intent = new Intent(action);
- intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.settings",
- "com.android.settings.wifi.WifiNoInternetDialog");
+ if (type != NotificationType.LOGGED_IN) {
+ intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+ }
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
@@ -3273,7 +3315,7 @@
!nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
return;
}
- showValidationNotification(nai, NotificationType.NO_INTERNET);
+ showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
@@ -3282,7 +3324,7 @@
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
- showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3428,6 +3470,9 @@
case EVENT_DATA_SAVER_CHANGED:
handleRestrictBackgroundChanged(toBool(msg.arg1));
break;
+ case EVENT_TIMEOUT_NOTIFICATION:
+ mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+ break;
}
}
}
@@ -3685,20 +3730,46 @@
}
}
+ /**
+ * Returns information about the proxy a certain network is using. If given a null network, it
+ * it will return the proxy for the bound network for the caller app or the default proxy if
+ * none.
+ *
+ * @param network the network we want to get the proxy information for.
+ * @return Proxy information if a network has a proxy configured, or otherwise null.
+ */
@Override
public ProxyInfo getProxyForNetwork(Network network) {
- if (network == null) return mProxyTracker.getDefaultProxy();
final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
if (globalProxy != null) return globalProxy;
- if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
- // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
- // caller may not have.
+ if (network == null) {
+ // Get the network associated with the calling UID.
+ final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+ true);
+ if (activeNetwork == null) {
+ return null;
+ }
+ return getLinkPropertiesProxyInfo(activeNetwork);
+ } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ return getLinkPropertiesProxyInfo(network);
+ }
+ // No proxy info available if the calling UID does not have network access.
+ return null;
+ }
+
+ @VisibleForTesting
+ protected boolean queryUserAccess(int uid, int netId) {
+ return NetworkUtils.queryUserAccess(uid, netId);
+ }
+
+ private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return null;
synchronized (nai) {
- final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
- if (proxyInfo == null) return null;
- return new ProxyInfo(proxyInfo);
+ final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
+ return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
}
}
@@ -3722,11 +3793,10 @@
mProxyTracker.setDefaultProxy(proxy);
}
- // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
- // This method gets called when any network changes proxy, but the broadcast only ever contains
- // the default proxy (even if it hasn't changed).
- // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
- // world where an app might be bound to a non-default network.
+ // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
+ // when any network changes proxy.
+ // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
+ // multi-network world where an app might be bound to a non-default network.
private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
@@ -5893,12 +5963,6 @@
}
scheduleUnvalidatedPrompt(networkAgent);
- if (networkAgent.isVPN()) {
- // Temporarily disable the default proxy (not global).
- mProxyTracker.setDefaultProxyEnabled(false);
- // TODO: support proxy per network.
- }
-
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
// capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5917,10 +5981,16 @@
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
- mProxyTracker.setDefaultProxyEnabled(true);
updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
+ if (networkAgent.isVPN()) {
+ // As the active or bound network changes for apps, broadcast the default proxy, as
+ // apps may need to update their proxy data. This is called after disconnecting from
+ // VPN to make sure we do not broadcast the old proxy data.
+ // TODO(b/122649188): send the broadcast only to VPN users.
+ mProxyTracker.sendProxyBroadcast();
+ }
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
// going into or coming out of SUSPEND: re-score and notify
@@ -6185,6 +6255,17 @@
}
@Override
+ public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+ int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
+ String dstAddr) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startNattKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, resourceId,
+ intervalSeconds, messenger, binder,
+ srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+ }
+
+ @Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8dcc1d5..d2c6354 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -220,6 +220,8 @@
private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
+ private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>();
+
@GuardedBy("mLock")
private final ArrayMap<IBinder, Identity> mGnssMeasurementsListeners = new ArrayMap<>();
@@ -353,6 +355,18 @@
}
}
}, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST),
+ true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ onIgnoreSettingsWhitelistChangedLocked();
+ }
+ }
+ }, UserHandle.USER_ALL);
new PackageMonitor() {
@Override
@@ -550,6 +564,25 @@
}
}
+ @GuardedBy("lock")
+ private void onIgnoreSettingsWhitelistChangedLocked() {
+ String setting = Settings.Global.getString(
+ mContext.getContentResolver(),
+ Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST);
+ if (setting == null) {
+ setting = "";
+ }
+
+ mIgnoreSettingsPackageWhitelist.clear();
+ mIgnoreSettingsPackageWhitelist.addAll(
+ SystemConfig.getInstance().getAllowIgnoreLocationSettings());
+ mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+
+ for (LocationProvider p : mProviders) {
+ applyRequirementsLocked(p);
+ }
+ }
+
@GuardedBy("mLock")
private void onUserProfilesChangedLocked() {
mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
@@ -1299,8 +1332,7 @@
if (provider == null) {
continue;
}
- if (!provider.isUseableLocked()
- && !updateRecord.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
continue;
}
@@ -1988,7 +2020,7 @@
}
// requests that ignore location settings will never provider notifications
- if (record.mRealRequest.isLocationSettingsIgnored()) {
+ if (isSettingsExemptLocked(record)) {
continue;
}
@@ -2052,8 +2084,7 @@
record.mReceiver.mAllowedResolutionLevel)) {
continue;
}
- if (!provider.isUseableLocked()
- && !record.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
continue;
}
@@ -2163,6 +2194,25 @@
return false;
}
+ @GuardedBy("mLock")
+ private boolean isSettingsExemptLocked(UpdateRecord record) {
+ if (!record.mRealRequest.isLocationSettingsIgnored()) {
+ return false;
+ }
+
+ if (mBackgroundThrottlePackageWhitelist.contains(record.mReceiver.mIdentity.mPackageName)) {
+ return true;
+ }
+
+ for (LocationProvider provider : mProviders) {
+ if (record.mReceiver.mIdentity.mPackageName.equals(provider.getPackageLocked())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private class UpdateRecord {
final String mProvider;
private final LocationRequest mRealRequest; // original request from client
@@ -2306,7 +2356,7 @@
}
// make getFastestInterval() the minimum of interval and fastest interval
if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
- request.setFastestInterval(request.getInterval());
+ sanitizedRequest.setFastestInterval(request.getInterval());
}
return sanitizedRequest;
}
@@ -2418,7 +2468,7 @@
oldRecord.disposeLocked(false);
}
- if (!provider.isUseableLocked() && !request.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
// Notify the listener that updates are currently disabled - but only if the request
// does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
@@ -3056,7 +3106,7 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
- if (!provider.isUseableLocked() && !r.mRealRequest.isLocationSettingsIgnored()) {
+ if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
continue;
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 16b12f1..1870f8d 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -2,6 +2,7 @@
"presubmit": [
{
"name": "FrameworksMockingServicesTests",
+ "file_patterns": ["AlarmManagerService\\.java"],
"options": [
{
"include-annotation": "android.platform.test.annotations.Presubmit"
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index aa23890..e543617 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2115,6 +2115,16 @@
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
+ if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
+ if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b24290f..0d1702d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1607,19 +1607,8 @@
}
} break;
case UPDATE_HTTP_PROXY_MSG: {
- ProxyInfo proxy = (ProxyInfo)msg.obj;
- String host = "";
- String port = "";
- String exclList = "";
- Uri pacFileUrl = Uri.EMPTY;
- if (proxy != null) {
- host = proxy.getHost();
- port = Integer.toString(proxy.getPort());
- exclList = proxy.getExclusionListAsString();
- pacFileUrl = proxy.getPacFileUrl();
- }
synchronized (ActivityManagerService.this) {
- mProcessList.setAllHttpProxyLocked(host, port, exclList, pacFileUrl);
+ mProcessList.setAllHttpProxyLocked();
}
} break;
case PROC_START_TIMEOUT_MSG: {
@@ -14656,8 +14645,7 @@
mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
break;
case Proxy.PROXY_CHANGE_ACTION:
- ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
- mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
+ mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
break;
case android.hardware.Camera.ACTION_NEW_PICTURE:
case android.hardware.Camera.ACTION_NEW_VIDEO:
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3223f61..003ddd1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1502,7 +1502,7 @@
mService.mNativeDebuggingApp = null;
}
- if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PREFER_CODE_INTEGRITY) != 0
+ if (app.info.isCodeIntegrityPreferred()
|| (app.info.isPrivilegedApp()
&& DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
@@ -1735,7 +1735,7 @@
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
- packageNames, visibleVolIds,
+ packageNames, visibleVolIds, /*useBlastulaPool=*/ false,
new String[] {PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
@@ -2384,14 +2384,14 @@
}
@GuardedBy("mService")
- void setAllHttpProxyLocked(String host, String port, String exclList, Uri pacFileUrl) {
+ void setAllHttpProxyLocked() {
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord r = mLruProcesses.get(i);
// Don't dispatch to isolated processes as they can't access
// ConnectivityManager and don't have network privileges anyway.
if (r.thread != null && !r.isolated) {
try {
- r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
+ r.thread.updateHttpProxy();
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update http proxy for: " +
r.info.processName);
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 8a3cdca..1559ba8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,40 +16,49 @@
package com.android.server.connectivity;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkAgentInfo;
-import android.net.ConnectivityManager;
+// TODO: Clean up imports and remove references of PacketKeepalive constants.
+
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK;
+import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT;
+import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS;
+import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.util.IpUtils;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
-import android.system.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import android.util.Pair;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
-
/**
* Manages packet keepalive requests.
*
@@ -300,7 +309,9 @@
}
}
- public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+ /** Handle keepalive events from lower layer. */
+ public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai,
+ @NonNull Message message) {
int slot = message.arg1;
int reason = message.arg2;
@@ -330,8 +341,18 @@
}
}
- public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
- IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+ * {@link android.net.SocketKeepalive}.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ int srcPort,
+ @NonNull String dstAddrString,
+ int dstPort) {
if (nai == null) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
return;
@@ -360,6 +381,56 @@
NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
}
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
+ * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
+ * resource index bound to the {@link UdpEncapsulationSocket} when creating by
+ * {@link com.android.server.IpSecService} to verify whether the given
+ * {@link UdpEncapsulationSocket} is legitimate.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
+ int resourceId,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ @NonNull String dstAddrString,
+ int dstPort) {
+ // Ensure that the socket is created by IpSecService.
+ if (!isNattKeepaliveSocketValid(fd, resourceId)) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Get src port to adopt old API.
+ int srcPort = 0;
+ try {
+ final SocketAddress srcSockAddr = Os.getsockname(fd);
+ srcPort = ((InetSocketAddress) srcSockAddr).getPort();
+ } catch (ErrnoException e) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Forward request to old API.
+ startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort,
+ dstAddrString, dstPort);
+ }
+
+ /**
+ * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
+ **/
+ public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
+ // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
+ // 2. If the fd is created from the system api, check that it's bounded. And
+ // call dup to keep the fd open.
+ // 3. If the fd is created from IpSecService, check if the resource ID is valid. And
+ // hold the resource needed in IpSecService.
+ if (null == fd) {
+ return false;
+ }
+ return true;
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("Packet keepalives:");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 9ea73fb..d0cff25 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -152,6 +152,10 @@
// Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected;
+ // Indicates the user was notified of a successful captive portal login since a portal was
+ // last detected.
+ public boolean captivePortalLoginNotified;
+
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with
@@ -618,18 +622,19 @@
}
public String toString() {
- return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
- "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
- "lp{" + linkProperties + "} " +
- "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " +
- "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} lingering{" + isLingering() + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
- "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
- "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
- "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
- "clat{" + clatd + "} " +
- "}";
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} "
+ + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
+ + "lp{" + linkProperties + "} "
+ + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ + "created{" + created + "} lingering{" + isLingering() + "} "
+ + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
+ + "clat{" + clatd + "} "
+ + "}";
}
public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476..b50477b 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,13 +16,16 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -31,15 +34,12 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.widget.Toast;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
public class NetworkNotificationManager {
@@ -47,7 +47,8 @@
LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
- SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+ SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+ LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN);
public final int eventId;
@@ -192,6 +193,9 @@
details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
}
+ } else if (notifyType == NotificationType.LOGGED_IN) {
+ title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+ details = r.getString(R.string.captive_portal_logged_in_detailed);
} else if (notifyType == NotificationType.NETWORK_SWITCH) {
String fromTransport = getTransportName(transportType);
String toTransport = getTransportName(getFirstTransportType(switchToNai));
@@ -239,6 +243,18 @@
}
}
+ /**
+ * Clear the notification with the given id, only if it matches the given type.
+ */
+ public void clearNotification(int id, NotificationType notifyType) {
+ final int previousEventId = mNotificationTypeMap.get(id);
+ final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+ if (notifyType != previousNotifyType) {
+ return;
+ }
+ clearNotification(id);
+ }
+
public void clearNotification(int id) {
if (mNotificationTypeMap.indexOfKey(id) < 0) {
return;
@@ -290,6 +306,10 @@
return (t != null) ? t.name() : "UNKNOWN";
}
+ /**
+ * A notification with a higher number will take priority over a notification with a lower
+ * number.
+ */
private static int priority(NotificationType t) {
if (t == null) {
return 0;
@@ -302,6 +322,7 @@
case NETWORK_SWITCH:
return 2;
case LOST_INTERNET:
+ case LOGGED_IN:
return 1;
default:
return 0;
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index fdddccd..a671287 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -309,22 +309,4 @@
}
}
}
-
- /**
- * Enable or disable the default proxy.
- *
- * This sets the flag for enabling/disabling the default proxy and sends the broadcast
- * if applicable.
- * @param enabled whether the default proxy should be enabled.
- */
- public void setDefaultProxyEnabled(final boolean enabled) {
- synchronized (mProxyLock) {
- if (mDefaultProxyEnabled != enabled) {
- mDefaultProxyEnabled = enabled;
- if (mGlobalProxy == null && mDefaultProxy != null) {
- sendProxyBroadcast();
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c72c9dd..62a1b03 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -793,6 +793,8 @@
}
}
+ lp.setHttpProxy(mConfig.proxyInfo);
+
if (!allowIPv4) {
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 65fc982..ad9ac12 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -50,6 +50,7 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* {@hide}
@@ -57,7 +58,7 @@
public class BackgroundDexOptService extends JobService {
private static final String TAG = "BackgroundDexOptService";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int JOB_IDLE_OPTIMIZE = 800;
private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -102,7 +103,6 @@
private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
private final File mDataDir = Environment.getDataDirectory();
-
private static final long mDowngradeUnusedAppsThresholdInMillis =
getDowngradeUnusedAppsThresholdInMillis();
@@ -275,21 +275,18 @@
long lowStorageThreshold = getLowStorageThreshold(context);
// Optimize primary apks.
- int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
- sFailedPackageNamesPrimary);
-
+ int result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ true);
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+ if (supportSecondaryDex()) {
result = reconcileSecondaryDexFiles(pm.getDexManager());
if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return result;
}
-
- result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
- sFailedPackageNamesSecondary);
+ result = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ false);
}
return result;
}
@@ -339,94 +336,86 @@
}
private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold, boolean is_for_primary_dex,
- ArraySet<String> failedPackageNames) {
+ long lowStorageThreshold, boolean isForPrimaryDex) {
ArraySet<String> updatedPackages = new ArraySet<>();
Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+ Log.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
// Only downgrade apps when space is low on device.
// Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
// up disk before user hits the actual lowStorageThreshold.
final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
lowStorageThreshold;
boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
+ Log.d(TAG, "Should Downgrade " + shouldDowngrade);
+ boolean dex_opt_performed = false;
for (String pkg : pkgs) {
int abort_code = abortIdleOptimizations(lowStorageThreshold);
if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
return abort_code;
}
-
- synchronized (failedPackageNames) {
- if (failedPackageNames.contains(pkg)) {
- // Skip previously failing package
- continue;
- }
- }
-
- int reason;
- boolean downgrade;
- long package_size_before = 0; //used when the app is downgraded
// Downgrade unused packages.
if (unusedPackages.contains(pkg) && shouldDowngrade) {
- package_size_before = getPackageSize(pm, pkg);
- // This applies for system apps or if packages location is not a directory, i.e.
- // monolithic install.
- if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
- // For apps that don't have the oat directory, instead of downgrading,
- // remove their compiler artifacts from dalvik cache.
- pm.deleteOatArtifactsOfPackage(pkg);
+ dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
+ } else {
+ if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
+ // can't dexopt because of low space.
continue;
- } else {
- reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- downgrade = true;
}
- } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
- reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- downgrade = false;
- } else {
- // can't dexopt because of low space.
- continue;
+ dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
}
-
- synchronized (failedPackageNames) {
- // Conservatively add package to the list of failing ones in case
- // performDexOpt never returns.
- failedPackageNames.add(pkg);
- }
-
- // Optimize package if needed. Note that there can be no race between
- // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- boolean success;
- int dexoptFlags =
- DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
- DexoptOptions.DEXOPT_BOOT_COMPLETE |
- (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
- DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
- if (is_for_primary_dex) {
- int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
- dexoptFlags));
- success = result != PackageDexOptimizer.DEX_OPT_FAILED;
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
- }
- } else {
- success = pm.performDexOpt(new DexoptOptions(pkg,
- reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
- }
- if (success) {
- // Dexopt succeeded, remove package from the list of failing ones.
- synchronized (failedPackageNames) {
- failedPackageNames.remove(pkg);
- }
- if (downgrade) {
- StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
- getPackageSize(pm, pkg), /*aggressive=*/ false);
- }
+ if (dex_opt_performed) {
+ updatedPackages.add(pkg);
}
}
+
notifyPinService(updatedPackages);
return OPTIMIZE_PROCESSED;
}
+
+ /**
+ * Try to downgrade the package to a smaller compilation filter.
+ * eg. if the package is in speed-profile the package will be downgraded to verify.
+ * @param pm PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean downgradePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ Log.d(TAG, "Downgrading " + pkg);
+ boolean dex_opt_performed = false;
+ int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
+ | DexoptOptions.DEXOPT_DOWNGRADE;
+ long package_size_before = getPackageSize(pm, pkg);
+
+ if (isForPrimaryDex) {
+ // This applies for system apps or if packages location is not a directory, i.e.
+ // monolithic install.
+ if (!pm.canHaveOatDir(pkg)) {
+ // For apps that don't have the oat directory, instead of downgrading,
+ // remove their compiler artifacts from dalvik cache.
+ pm.deleteOatArtifactsOfPackage(pkg);
+ } else {
+ dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+ }
+ } else {
+ dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
+
+ if (dex_opt_performed) {
+ StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
+ getPackageSize(pm, pkg), /*aggressive=*/ false);
+ }
+ return dex_opt_performed;
+ }
+
+ private boolean supportSecondaryDex() {
+ return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+ }
+
private int reconcileSecondaryDexFiles(DexManager dm) {
// TODO(calin): should we blacklist packages for which we fail to reconcile?
for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
@@ -438,6 +427,73 @@
return OPTIMIZE_PROCESSED;
}
+ /**
+ *
+ * Optimize package if needed. Note that there can be no race between
+ * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
+ * @param pm An instance of PackageManagerService
+ * @param pkg The package to be downgraded.
+ * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+ * @return true if the package was downgraded.
+ */
+ private boolean optimizePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex) {
+ int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+
+ return isForPrimaryDex
+ ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
+ : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
+
+ private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+ () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+ int dexoptFlags) {
+ DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
+ dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
+ int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+ () -> pm.performDexOpt(dexoptOptions)
+ ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
+ );
+ return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
+ }
+
+ /**
+ * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
+ * the package is added to the list of failed packages.
+ * Return one of following result:
+ * {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
+ * {@link PackageDexOptimizer#DEX_OPT_FAILED}
+ */
+ private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
+ Supplier<Integer> performDexOptWrapper) {
+ ArraySet<String> sFailedPackageNames =
+ isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
+ synchronized (sFailedPackageNames) {
+ if (sFailedPackageNames.contains(pkg)) {
+ // Skip previously failing package
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ }
+ sFailedPackageNames.add(pkg);
+ }
+ int result = performDexOptWrapper.get();
+ if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
+ synchronized (sFailedPackageNames) {
+ sFailedPackageNames.remove(pkg);
+ }
+ }
+ return result;
+ }
+
// Evaluate whether or not idle optimizations should continue.
private int abortIdleOptimizations(long lowStorageThreshold) {
if (mAbortIdleOptimization.get()) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a33f14b..d0ef4f1 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -25,6 +25,7 @@
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -135,6 +136,7 @@
private final Context mContext;
private final UserManager mUm;
private final UserManagerInternal mUserManagerInternal;
+ private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,6 +158,8 @@
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mUserManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(UserManagerInternal.class));
+ mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ LocalServices.getService(UsageStatsManagerInternal.class));
mActivityManagerInternal = Preconditions.checkNotNull(
LocalServices.getService(ActivityManagerInternal.class));
mActivityTaskManagerInternal = Preconditions.checkNotNull(
@@ -671,6 +675,30 @@
}
}
+ @Override
+ public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
+ String packageName, UserHandle user) {
+ verifyCallingPackage(callingPackage);
+ if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
+ return null;
+ }
+
+ final PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ComponentName cn = pmi.getDefaultHomeActivity(user.getIdentifier());
+ if (!cn.getPackageName().equals(callingPackage)) {
+ throw new SecurityException("Caller is not the active launcher");
+ }
+
+ final UsageStatsManagerInternal.AppUsageLimitData data =
+ mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
+ if (data == null) {
+ return null;
+ }
+ return new LauncherApps.AppUsageLimit(
+ data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+ }
+
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5412e94..94b1b36 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -506,8 +506,10 @@
boolean isUsedByOtherApps) {
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
- // When a priv app is configured to run out of box, only verify it.
- if (info.isPrivilegedApp() && DexManager.isPackageSelectedToRunOob(info.packageName)) {
+ // When an app or priv app is configured to run out of box, only verify it.
+ if (info.isCodeIntegrityPreferred()
+ || (info.isPrivilegedApp()
+ && DexManager.isPackageSelectedToRunOob(info.packageName))) {
return "verify";
}
if (vmSafeMode) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 3562630..692c032 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2307,7 +2307,7 @@
sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
break;
case "--apex":
- sessionParams.installFlags |= PackageManager.INSTALL_APEX;
+ sessionParams.setInstallAsApex();
sessionParams.setStaged();
break;
case "--multi-package":
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 41cab2d..0e40a00 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1901,9 +1901,8 @@
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return handleStartTransitionForKeyguardLw(transit, duration);
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 1d74350..ab2807a 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -104,6 +104,7 @@
public static final int REASON_SETTING_CHANGED = 8;
public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON = 9;
public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF = 10;
+ public static final int REASON_STICKY_RESTORE_OFF = 13;
/**
* Plugin interface. All methods are guaranteed to be called on the same (handler) thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index b7f28da..404e450 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -28,7 +28,6 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -85,42 +84,56 @@
@GuardedBy("mLock")
private boolean mIsBatteryLevelLow;
- /** Previously known value of Global.LOW_POWER_MODE. */
+ /** Previously known value of Settings.Global.LOW_POWER_MODE. */
@GuardedBy("mLock")
private boolean mSettingBatterySaverEnabled;
- /** Previously known value of Global.LOW_POWER_MODE_STICKY. */
+ /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */
@GuardedBy("mLock")
private boolean mSettingBatterySaverEnabledSticky;
/** Config flag to track if battery saver's sticky behaviour is disabled. */
private final boolean mBatterySaverStickyBehaviourDisabled;
+ /**
+ * Whether or not to end sticky battery saver upon reaching a level specified by
+ * {@link #mSettingBatterySaverStickyAutoDisableThreshold}.
+ */
+ @GuardedBy("mLock")
+ private boolean mSettingBatterySaverStickyAutoDisableEnabled;
+
+ /**
+ * The battery level at which to end sticky battery saver. Only useful if
+ * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}.
+ */
+ @GuardedBy("mLock")
+ private int mSettingBatterySaverStickyAutoDisableThreshold;
+
/** Config flag to track default disable threshold for Dynamic Power Savings enabled battery
* saver. */
@GuardedBy("mLock")
private final int mDynamicPowerSavingsDefaultDisableThreshold;
/**
- * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
+ * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL.
* (Currently only used in dumpsys.)
*/
@GuardedBy("mLock")
private int mSettingBatterySaverTriggerThreshold;
- /** Previously known value of Global.AUTOMATIC_POWER_SAVER_MODE. */
+ /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVER_MODE. */
@GuardedBy("mLock")
private int mSettingAutomaticBatterySaver;
/** When to disable battery saver again if it was enabled due to an external suggestion.
- * Corresponds to Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
+ * Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
*/
@GuardedBy("mLock")
private int mDynamicPowerSavingsDisableThreshold;
/**
* Whether we've received a suggestion that battery saver should be on from an external app.
- * Updates when Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
+ * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
*/
@GuardedBy("mLock")
private boolean mDynamicPowerSavingsBatterySaver;
@@ -181,7 +194,7 @@
Slog.d(TAG, "onBootCompleted");
}
// Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it.
- putGlobalSetting(Global.LOW_POWER_MODE, 0);
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0);
// This is called with the power manager lock held. Don't do anything that may call to
// upper services. (e.g. don't call into AM directly)
@@ -199,13 +212,19 @@
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.AUTOMATIC_POWER_SAVER_MODE),
+ Settings.Global.AUTOMATIC_POWER_SAVER_MODE),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.DYNAMIC_POWER_SAVINGS_ENABLED),
+ Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
cr.registerContentObserver(Settings.Global.getUriFor(
- Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+ Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL),
false, mSettingsObserver, UserHandle.USER_SYSTEM);
synchronized (mLock) {
@@ -239,25 +258,31 @@
}
@GuardedBy("mLock")
- void refreshSettingsLocked() {
+ private void refreshSettingsLocked() {
final boolean lowPowerModeEnabled = getGlobalSetting(
Settings.Global.LOW_POWER_MODE, 0) != 0;
final boolean lowPowerModeEnabledSticky = getGlobalSetting(
Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting(
- Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
+ Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0;
final int lowPowerModeTriggerLevel = getGlobalSetting(
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
- final int automaticBatterySaver = getGlobalSetting(
- Global.AUTOMATIC_POWER_SAVER_MODE,
+ final int automaticBatterySaverMode = getGlobalSetting(
+ Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
PowerManager.POWER_SAVER_MODE_PERCENTAGE);
final int dynamicPowerSavingsDisableThreshold = getGlobalSetting(
- Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
+ Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
mDynamicPowerSavingsDefaultDisableThreshold);
+ final boolean isStickyAutoDisableEnabled = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0;
+ final int stickyAutoDisableThreshold = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
- lowPowerModeTriggerLevel, automaticBatterySaver, dynamicPowerSavingsBatterySaver,
- dynamicPowerSavingsDisableThreshold);
+ lowPowerModeTriggerLevel,
+ isStickyAutoDisableEnabled, stickyAutoDisableThreshold,
+ automaticBatterySaverMode,
+ dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold);
}
/**
@@ -269,12 +294,16 @@
@GuardedBy("mLock")
@VisibleForTesting
void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
- int batterySaverTriggerThreshold, int automaticBatterySaver,
+ int batterySaverTriggerThreshold,
+ boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold,
+ int automaticBatterySaver,
boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) {
if (DEBUG) {
Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
+ " sticky=" + batterySaverEnabledSticky
+ " threshold=" + batterySaverTriggerThreshold
+ + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled
+ + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold
+ " automaticBatterySaver=" + automaticBatterySaver
+ " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver
+ " dynamicPowerSavingsDisableThreshold="
@@ -283,11 +312,19 @@
mSettingsLoaded = true;
+ // Set sensible limits.
+ stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold,
+ batterySaverTriggerThreshold);
+
final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
final boolean stickyChanged =
mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
final boolean thresholdChanged
= mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
+ final boolean stickyAutoDisableEnabledChanged =
+ mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled;
+ final boolean stickyAutoDisableThresholdChanged =
+ mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold;
final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver;
final boolean dynamicPowerSavingsThresholdChanged =
mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold;
@@ -295,6 +332,7 @@
mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver;
if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged
+ || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged
|| dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) {
return;
}
@@ -302,6 +340,8 @@
mSettingBatterySaverEnabled = batterySaverEnabled;
mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
+ mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled;
+ mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold;
mSettingAutomaticBatterySaver = automaticBatterySaver;
mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold;
mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver;
@@ -376,7 +416,9 @@
+ " mBatterySaverSnoozing=" + mBatterySaverSnoozing
+ " mIsPowered=" + mIsPowered
+ " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver
- + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky);
+ + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky
+ + " mSettingBatterySaverStickyAutoDisableEnabled="
+ + mSettingBatterySaverStickyAutoDisableEnabled);
}
if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
return; // Not fully initialized yet.
@@ -392,10 +434,15 @@
"Plugged in");
} else if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) {
- // Re-enable BS.
- enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
- BatterySaverController.REASON_STICKY_RESTORE,
- "Sticky restore");
+ if (mSettingBatterySaverStickyAutoDisableEnabled
+ && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold) {
+ setStickyActive(false);
+ } else {
+ // Re-enable BS.
+ enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true,
+ BatterySaverController.REASON_STICKY_RESTORE,
+ "Sticky restore");
+ }
} else if (mSettingAutomaticBatterySaver
== PowerManager.POWER_SAVER_MODE_PERCENTAGE
@@ -483,12 +530,10 @@
}
mSettingBatterySaverEnabled = enable;
- putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0);
if (manual) {
- mSettingBatterySaverEnabledSticky = !mBatterySaverStickyBehaviourDisabled && enable;
- putGlobalSetting(Global.LOW_POWER_MODE_STICKY,
- mSettingBatterySaverEnabledSticky ? 1 : 0);
+ setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable);
}
mBatterySaverController.enableBatterySaver(enable, intReason);
@@ -506,7 +551,8 @@
}
}
- private void triggerDynamicModeNotification() {
+ @VisibleForTesting
+ void triggerDynamicModeNotification() {
NotificationManager manager = mContext.getSystemService(NotificationManager.class);
ensureNotificationChannelExists(manager);
@@ -553,14 +599,20 @@
mBatterySaverSnoozing = snoozing;
}
+ private void setStickyActive(boolean active) {
+ mSettingBatterySaverEnabledSticky = active;
+ putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY,
+ mSettingBatterySaverEnabledSticky ? 1 : 0);
+ }
+
@VisibleForTesting
protected void putGlobalSetting(String key, int value) {
- Global.putInt(mContext.getContentResolver(), key, value);
+ Settings.Global.putInt(mContext.getContentResolver(), key, value);
}
@VisibleForTesting
protected int getGlobalSetting(String key, int defValue) {
- return Global.getInt(mContext.getContentResolver(), key, defValue);
+ return Settings.Global.getInt(mContext.getContentResolver(), key, defValue);
}
public void dump(PrintWriter pw) {
@@ -597,6 +649,10 @@
pw.println(mSettingBatterySaverEnabled);
pw.print(" mSettingBatterySaverEnabledSticky=");
pw.println(mSettingBatterySaverEnabledSticky);
+ pw.print(" mSettingBatterySaverStickyAutoDisableEnabled=");
+ pw.println(mSettingBatterySaverStickyAutoDisableEnabled);
+ pw.print(" mSettingBatterySaverStickyAutoDisableThreshold=");
+ pw.println(mSettingBatterySaverStickyAutoDisableThreshold);
pw.print(" mSettingBatterySaverTriggerThreshold=");
pw.println(mSettingBatterySaverTriggerThreshold);
pw.print(" mBatterySaverStickyBehaviourDisabled=");
@@ -628,6 +684,13 @@
mSettingBatterySaverEnabledSticky);
proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
mSettingBatterySaverTriggerThreshold);
+ proto.write(
+ BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED,
+ mSettingBatterySaverStickyAutoDisableEnabled);
+ proto.write(
+ BatterySaverStateMachineProto
+ .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD,
+ mSettingBatterySaverStickyAutoDisableThreshold);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/rollback/RollbackData.java b/services/core/java/com/android/server/rollback/RollbackData.java
index 4015c4f..a4f3064 100644
--- a/services/core/java/com/android/server/rollback/RollbackData.java
+++ b/services/core/java/com/android/server/rollback/RollbackData.java
@@ -49,6 +49,14 @@
*/
public Instant timestamp;
+ /**
+ * Whether this Rollback is currently in progress. This field is true from the point
+ * we commit a {@code PackageInstaller} session containing these packages to the point the
+ * {@code PackageInstaller} calls into the {@code onFinished} callback.
+ */
+ // NOTE: All accesses to this field are from the RollbackManager handler thread.
+ public boolean inProgress = false;
+
RollbackData(int rollbackId, File backupDir) {
this.rollbackId = rollbackId;
this.backupDir = backupDir;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 7f515bf..693d5d6 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -285,14 +285,19 @@
Log.i(TAG, "Initiating rollback of " + targetPackageName);
// Get the latest RollbackData for the target package.
- RollbackData data = getRollbackForPackage(targetPackageName);
+ final RollbackData data = getRollbackForPackage(targetPackageName);
if (data == null) {
sendFailure(statusReceiver, "No rollback available for package.");
return;
}
if (data.rollbackId != rollback.getRollbackId()) {
- sendFailure(statusReceiver, "Rollback for package is out of date");
+ sendFailure(statusReceiver, "Rollback for package is out of date.");
+ return;
+ }
+
+ if (data.inProgress) {
+ sendFailure(statusReceiver, "Rollback for package is already in progress.");
return;
}
@@ -371,6 +376,10 @@
final LocalIntentReceiver receiver = new LocalIntentReceiver(
(Intent result) -> {
+ // We've now completed the rollback, so we mark it as no longer in
+ // progress.
+ data.inProgress = false;
+
int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
@@ -392,6 +401,7 @@
}
);
+ data.inProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
Log.e(TAG, "Unable to roll back " + targetPackageName, e);
@@ -774,10 +784,15 @@
getHandler().post(() -> {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- // TODO(narayan): Should we make sure we're in the middle of a session commit for a
- // a package with this package name ? Otherwise it's possible we may roll back data
- // for some other downgrade.
- if (getRollbackForPackage(packageName) == null) {
+ final RollbackData rollbackData = getRollbackForPackage(packageName);
+ if (rollbackData == null) {
+ pmi.finishPackageInstall(token, false);
+ return;
+ }
+
+ if (!rollbackData.inProgress) {
+ Log.e(TAG, "Request to restore userData for: " + packageName
+ + ", but no rollback in progress.");
pmi.finishPackageInstall(token, false);
return;
}
diff --git a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
index 56db32a..146c516 100644
--- a/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
+++ b/services/core/java/com/android/server/signedconfig/SignatureVerifier.java
@@ -41,8 +41,8 @@
private static final boolean DBG = false;
private static final String DEBUG_KEY =
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60"
- + "pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==";
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1"
+ + "x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==";
private static final String PROD_KEY =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+lky6wKyGL6lE1VrD0YTMHwb0Xwc+tzC8MvnrzVxodvTp"
+ "VY/jV7V+Zktcx+pry43XPABFRXtbhTo+qykhyBA1g==";
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 40664fe..acede7d 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -1705,6 +1705,27 @@
}
}
+ private void pullTemperature(int tagId, long elapsedNanos, long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ long callingToken = Binder.clearCallingIdentity();
+ try {
+ List<Temperature> temperatures = sThermalService.getCurrentTemperatures();
+ for (Temperature temp : temperatures) {
+ StatsLogEventWrapper e =
+ new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(temp.getType());
+ e.writeString(temp.getName());
+ e.writeInt((int) (temp.getValue() * 10));
+ pulledData.add(e);
+ }
+ } catch (RemoteException e) {
+ // Should not happen.
+ Slog.e(TAG, "Disconnected from thermal service. Cannot pull temperatures.");
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
/**
* Pulls various data.
*/
@@ -1867,6 +1888,10 @@
pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.TEMPERATURE: {
+ pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5f393ef..f3a363a 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -174,6 +174,7 @@
private int mLastUsedAppTransition = TRANSIT_UNSET;
private String mLastOpeningApp;
private String mLastClosingApp;
+ private String mLastChangingApp;
private static final int NEXT_TRANSIT_TYPE_NONE = 0;
private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
@@ -318,14 +319,16 @@
private void setAppTransition(int transit, int flags) {
mNextAppTransition = transit;
mNextAppTransitionFlags |= flags;
- setLastAppTransition(TRANSIT_UNSET, null, null);
+ setLastAppTransition(TRANSIT_UNSET, null, null, null);
updateBooster();
}
- void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp) {
+ void setLastAppTransition(int transit, AppWindowToken openingApp, AppWindowToken closingApp,
+ AppWindowToken changingApp) {
mLastUsedAppTransition = transit;
mLastOpeningApp = "" + openingApp;
mLastClosingApp = "" + closingApp;
+ mLastChangingApp = "" + changingApp;
}
boolean isReady() {
@@ -412,9 +415,7 @@
* @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
* layout pass needs to be done
*/
- int goodToGo(int transit, AppWindowToken topOpeningApp,
- AppWindowToken topClosingApp, ArraySet<AppWindowToken> openingApps,
- ArraySet<AppWindowToken> closingApps) {
+ int goodToGo(int transit, AppWindowToken topOpeningApp, ArraySet<AppWindowToken> openingApps) {
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
@@ -422,8 +423,6 @@
? topOpeningApp.getAnimation()
: null;
int redoLayout = notifyAppTransitionStartingLocked(transit,
- topOpeningApp != null ? topOpeningApp.token : null,
- topClosingApp != null ? topClosingApp.token : null,
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
@@ -500,13 +499,12 @@
}
}
- private int notifyAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ private int notifyAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, openToken,
- closeToken, duration, statusBarAnimationStartTime, statusBarAnimationDuration);
+ redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(transit, duration,
+ statusBarAnimationStartTime, statusBarAnimationDuration);
}
return redoLayout;
}
@@ -2128,6 +2126,8 @@
pw.println(mLastOpeningApp);
pw.print(prefix); pw.print("mLastClosingApp=");
pw.println(mLastClosingApp);
+ pw.print(prefix); pw.print("mLastChangingApp=");
+ pw.println(mLastChangingApp);
}
}
@@ -2226,14 +2226,16 @@
if (dc == null) {
return;
}
- if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) {
+ if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()
+ || !dc.mChangingApps.isEmpty()) {
if (DEBUG_APP_TRANSITIONS) {
Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
+ " displayId=" + dc.getDisplayId()
+ " isTransitionSet()="
+ dc.mAppTransition.isTransitionSet()
+ " mOpeningApps.size()=" + dc.mOpeningApps.size()
- + " mClosingApps.size()=" + dc.mClosingApps.size());
+ + " mClosingApps.size()=" + dc.mClosingApps.size()
+ + " mChangingApps.size()=" + dc.mChangingApps.size());
}
setTimeout();
mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 775dfa42..49308b8 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -89,8 +89,9 @@
* Handle application transition for given display.
*/
void handleAppTransitionReady() {
- final int appsCount = mDisplayContent.mOpeningApps.size();
- if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
+ mTempTransitionReasons.clear();
+ if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
+ || !transitionGoodToGo(mDisplayContent.mChangingApps, mTempTransitionReasons)) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
@@ -108,21 +109,25 @@
mDisplayContent.mWallpaperMayChange = false;
- int i;
- for (i = 0; i < appsCount; i++) {
- final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+ int appCount = mDisplayContent.mOpeningApps.size();
+ for (int i = 0; i < appCount; ++i) {
// Clearing the mAnimatingExit flag before entering animation. It's set to true if app
// window is removed, or window relayout to invisible. This also affects window
// visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
// transition selection depends on wallpaper target visibility.
- wtoken.clearAnimatingFlags();
+ mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags();
+ }
+ appCount = mDisplayContent.mChangingApps.size();
+ for (int i = 0; i < appCount; ++i) {
+ // Clearing for same reason as above.
+ mDisplayContent.mChangingApps.valueAtUnchecked(i).clearAnimatingFlags();
}
// Adjust wallpaper before we pull the lower/upper target, since pending changes
// (like the clearAnimatingFlags() above) might affect wallpaper target result.
// Or, the opening app window should be a wallpaper target.
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
- mDisplayContent.mOpeningApps);
+ mDisplayContent.mOpeningApps, mDisplayContent.mChangingApps);
// Determine if closing and opening app token sets are wallpaper targets, in which case
// special animations are needed.
@@ -141,7 +146,7 @@
// no need to do an animation. This is the case, for example, when this transition is being
// done behind a dream window.
final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
- mDisplayContent.mClosingApps);
+ mDisplayContent.mClosingApps, mDisplayContent.mChangingApps);
final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw();
final AppWindowToken animLpToken = allowAnimations
? findAnimLayoutParamsToken(transit, activityTypes)
@@ -152,11 +157,15 @@
final AppWindowToken topClosingApp = allowAnimations
? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */)
: null;
+ final AppWindowToken topChangingApp = allowAnimations
+ ? getTopApp(mDisplayContent.mChangingApps, false /* ignoreHidden */)
+ : null;
final WindowManager.LayoutParams animLp = getAnimLp(animLpToken);
overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes);
final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
- || containsVoiceInteraction(mDisplayContent.mOpeningApps);
+ || containsVoiceInteraction(mDisplayContent.mOpeningApps)
+ || containsVoiceInteraction(mDisplayContent.mChangingApps);
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
@@ -165,13 +174,14 @@
handleClosingApps(transit, animLp, voiceInteraction);
handleOpeningApps(transit, animLp, voiceInteraction);
+ handleChangingApps(transit, animLp, voiceInteraction);
appTransition.setLastAppTransition(transit, topOpeningApp,
- topClosingApp);
+ topClosingApp, topChangingApp);
final int flags = appTransition.getTransitFlags();
layoutRedo = appTransition.goodToGo(transit, topOpeningApp,
- topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps);
+ mDisplayContent.mOpeningApps);
handleNonAppWindowsInTransition(transit, flags);
appTransition.postAnimationCallback();
appTransition.clear();
@@ -183,6 +193,7 @@
mDisplayContent.mOpeningApps.clear();
mDisplayContent.mClosingApps.clear();
+ mDisplayContent.mChangingApps.clear();
mDisplayContent.mUnknownAppVisibilityController.clear();
// This has changed the visibility of windows, so perform
@@ -237,29 +248,30 @@
AppWindowToken result;
final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+ final ArraySet<AppWindowToken> changingApps = mDisplayContent.mChangingApps;
// Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
- result = lookForHighestTokenWithFilter(closingApps, openingApps,
+ result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.getRemoteAnimationDefinition() != null
&& w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
if (result != null) {
return result;
}
- result = lookForHighestTokenWithFilter(closingApps, openingApps,
+ result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.fillsParent() && w.findMainWindow() != null);
if (result != null) {
return result;
}
- return lookForHighestTokenWithFilter(closingApps, openingApps,
+ return lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.findMainWindow() != null);
}
/**
* @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
- * of apps in {@code array1} and {@code array2}.
+ * of apps in {@code array1}, {@code array2}, and {@code array3}.
*/
private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
- ArraySet<AppWindowToken> array2) {
+ ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3) {
final ArraySet<Integer> result = new ArraySet<>();
for (int i = array1.size() - 1; i >= 0; i--) {
result.add(array1.valueAt(i).getActivityType());
@@ -267,19 +279,26 @@
for (int i = array2.size() - 1; i >= 0; i--) {
result.add(array2.valueAt(i).getActivityType());
}
+ for (int i = array3.size() - 1; i >= 0; i--) {
+ result.add(array3.valueAt(i).getActivityType());
+ }
return result;
}
private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1,
- ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) {
- final int array1count = array1.size();
- final int count = array1count + array2.size();
+ ArraySet<AppWindowToken> array2, ArraySet<AppWindowToken> array3,
+ Predicate<AppWindowToken> filter) {
+ final int array2base = array1.size();
+ final int array3base = array2.size() + array2base;
+ final int count = array3base + array3.size();
int bestPrefixOrderIndex = Integer.MIN_VALUE;
AppWindowToken bestToken = null;
for (int i = 0; i < count; i++) {
- final AppWindowToken wtoken = i < array1count
+ final AppWindowToken wtoken = i < array2base
? array1.valueAt(i)
- : array2.valueAt(i - array1count);
+ : (i < array3base
+ ? array2.valueAt(i - array2base)
+ : array3.valueAt(i - array3base));
final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
bestPrefixOrderIndex = prefixOrderIndex;
@@ -360,6 +379,24 @@
}
}
+ private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+ final ArraySet<AppWindowToken> apps = mDisplayContent.mChangingApps;
+ final int appsCount = apps.size();
+ for (int i = 0; i < appsCount; i++) {
+ AppWindowToken wtoken = apps.valueAt(i);
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now changing app" + wtoken);
+ wtoken.cancelAnimationOnly();
+ wtoken.applyAnimationLocked(null, transit, true, false);
+ wtoken.updateReportedVisibilityLocked();
+ mService.openSurfaceTransaction();
+ try {
+ wtoken.showAllWindowsLocked();
+ } finally {
+ mService.closeSurfaceTransaction("handleChangingApps");
+ }
+ }
+ }
+
private void handleNonAppWindowsInTransition(int transit, int flags) {
if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
@@ -379,16 +416,15 @@
}
}
- private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
+ private boolean transitionGoodToGo(ArraySet<AppWindowToken> apps, SparseIntArray outReasons) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
- "Checking " + appsCount + " opening apps (frozen="
+ "Checking " + apps.size() + " opening apps (frozen="
+ mService.mDisplayFrozen + " timeout="
+ mDisplayContent.mAppTransition.isTimeout() + ")...");
final ScreenRotationAnimation screenRotationAnimation =
mService.mAnimator.getScreenRotationAnimationLocked(
Display.DEFAULT_DISPLAY);
- outReasons.clear();
if (!mDisplayContent.mAppTransition.isTimeout()) {
// Imagine the case where we are changing orientation due to an app transition, but a
// previous orientation change is still in progress. We won't process the orientation
@@ -404,8 +440,8 @@
}
return false;
}
- for (int i = 0; i < appsCount; i++) {
- AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+ for (int i = 0; i < apps.size(); i++) {
+ AppWindowToken wtoken = apps.valueAt(i);
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index bb38f30..29645f6 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -50,9 +50,23 @@
private final SurfaceAnimator mSurfaceAnimator;
private final int mWidth;
private final int mHeight;
+ private final boolean mRelative;
AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
+ this(t, appToken, thumbnailHeader, false /* relative */);
+ }
+
+ /**
+ * @param t Transaction to create the thumbnail in.
+ * @param appToken {@link AppWindowToken} to associate this thumbnail with.
+ * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
+ * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
+ * relative to it) or not.
+ */
+ AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
+ boolean relative) {
mAppToken = appToken;
+ mRelative = relative;
mSurfaceAnimator =
new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService);
mWidth = thumbnailHeader.getWidth();
@@ -86,6 +100,9 @@
// We parent the thumbnail to the task, and just place it on top of anything else in the
// task.
t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+ if (relative) {
+ t.reparent(mSurfaceControl, appToken.getSurfaceControl());
+ }
}
void startAnimation(Transaction t, Animation anim) {
@@ -101,6 +118,13 @@
mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */);
}
+ /**
+ * Start animation with existing adapter.
+ */
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
+ mSurfaceAnimator.startAnimation(t, anim, hidden);
+ }
+
private void onAnimationFinished() {
}
@@ -147,6 +171,9 @@
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
t.setLayer(leash, Integer.MAX_VALUE);
+ if (mRelative) {
+ t.reparent(leash, mAppToken.getSurfaceControl());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 750c5ca..65b36a0 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -33,6 +34,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_UNSET;
import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
@@ -97,6 +99,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
@@ -117,6 +120,7 @@
import com.android.server.display.ColorDisplayService;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
@@ -261,7 +265,18 @@
/** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
private boolean mLastSurfaceShowing = true;
+ /**
+ * This gets used during some open/close transitions as well as during a change transition
+ * where it represents the starting-state snapshot.
+ */
private AppWindowThumbnail mThumbnail;
+ private final Rect mTransitStartRect = new Rect();
+
+ /**
+ * This leash is used to "freeze" the app surface in place after the state change, but before
+ * the animation is ready to start.
+ */
+ private SurfaceControl mTransitChangeLeash = null;
/** Have we been asked to have this token keep the screen frozen? */
private boolean mFreezingScreen;
@@ -272,6 +287,7 @@
private final Point mTmpPoint = new Point();
private final Rect mTmpRect = new Rect();
+ private final Rect mTmpPrevBounds = new Rect();
private RemoteAnimationDefinition mRemoteAnimationDefinition;
private AnimatingAppWindowTokenRegistry mAnimatingAppWindowTokenRegistry;
@@ -810,6 +826,7 @@
boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
getDisplayContent().mOpeningApps.remove(this);
+ getDisplayContent().mChangingApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mTaskSnapshotController.onAppRemoved(this);
waitingToShow = false;
@@ -1528,6 +1545,7 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWinMode = getWindowingMode();
+ mTmpPrevBounds.set(getBounds());
super.onConfigurationChanged(newParentConfig);
final int winMode = getWindowingMode();
@@ -1559,9 +1577,68 @@
mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
stackBounds);
}
+ } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
+ initializeChangeTransition(mTmpPrevBounds);
}
}
+ private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+ if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) {
+ return false;
+ }
+ // Only do an animation into and out-of freeform mode for now. Other mode
+ // transition animations are currently handled by system-ui.
+ return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
+ }
+
+ /**
+ * Initializes a change transition. Because the app is visible already, there is a small period
+ * of time where the user can see the app content/window update before the transition starts.
+ * To prevent this, we immediately take a snapshot and place the app/snapshot into a leash which
+ * "freezes" the location/crop until the transition starts.
+ * <p>
+ * Here's a walk-through of the process:
+ * 1. Create a temporary leash ("interim-change-leash") and reparent the app to it.
+ * 2. Set the temporary leash's position/crop to the current state.
+ * 3. Create a snapshot and place that at the top of the leash to cover up content changes.
+ * 4. Once the transition is ready, it will reparent the app to the animation leash.
+ * 5. Detach the interim-change-leash.
+ */
+ private void initializeChangeTransition(Rect startBounds) {
+ mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
+ false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+ mDisplayContent.mChangingApps.add(this);
+ mTransitStartRect.set(startBounds);
+
+ final SurfaceControl.Builder builder = makeAnimationLeash()
+ .setParent(getAnimationLeashParent())
+ .setName(getSurfaceControl() + " - interim-change-leash");
+ mTransitChangeLeash = builder.build();
+ Transaction t = getPendingTransaction();
+ t.setWindowCrop(mTransitChangeLeash, startBounds.width(), startBounds.height());
+ t.setPosition(mTransitChangeLeash, startBounds.left, startBounds.top);
+ t.show(mTransitChangeLeash);
+ t.reparent(getSurfaceControl(), mTransitChangeLeash);
+ onAnimationLeashCreated(t, mTransitChangeLeash);
+
+ if (mThumbnail == null && getTask() != null) {
+ final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController;
+ final ArraySet<Task> tasks = new ArraySet<>();
+ tasks.add(getTask());
+ snapshotCtrl.snapshotTasks(tasks);
+ snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks);
+ final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot(
+ getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */,
+ false /* reducedResolution */);
+ mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(),
+ true /* relative */);
+ }
+ }
+
+ boolean isInChangeTransition() {
+ return mTransitChangeLeash != null || isChangeTransition(mTransit);
+ }
+
@Override
void checkAppWindowsReadyToShow() {
if (allDrawn == mLastAllDrawn) {
@@ -2242,6 +2319,15 @@
return getBounds();
}
+ private static boolean isChangeTransition(int transit) {
+ return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+ }
+
+ private int getDefaultChangeTransitionDuration() {
+ return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
+ * mWmService.getTransitionAnimationScaleLocked());
+ }
+
boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction) {
@@ -2260,13 +2346,35 @@
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
if (okToAnimate()) {
final AnimationAdapter adapter;
+ AnimationAdapter thumbnailAdapter = null;
getAnimationBounds(mTmpPoint, mTmpRect);
+ boolean isChanging = isChangeTransition(transit) && mThumbnail != null;
+
// Delaying animation start isn't compatible with remote animations at all.
if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
&& !mSurfaceAnimator.isAnimationStartDelayed()) {
- adapter = getDisplayContent().mAppTransition.getRemoteAnimationController()
- .createAnimationAdapter(this, mTmpPoint, mTmpRect);
+ RemoteAnimationRecord adapters =
+ getDisplayContent().mAppTransition.getRemoteAnimationController()
+ .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
+ (isChanging ? mTransitStartRect : null));
+ adapter = adapters.mAdapter;
+ thumbnailAdapter = adapters.mThumbnailAdapter;
+ } else if (isChanging) {
+ int duration = getDefaultChangeTransitionDuration();
+ mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
+ adapter = new LocalAnimationAdapter(
+ new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+ getDisplayContent().getDisplayInfo(), duration,
+ true /* isAppAnimation */, false /* isThumbnail */),
+ mWmService.mSurfaceAnimationRunner);
+ thumbnailAdapter = new LocalAnimationAdapter(
+ new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
+ getDisplayContent().getDisplayInfo(), duration,
+ true /* isAppAnimation */, true /* isThumbnail */),
+ mWmService.mSurfaceAnimationRunner);
+ mTransit = transit;
+ mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
} else {
final int appStackClipMode =
getDisplayContent().mAppTransition.getAppStackClipMode();
@@ -2294,6 +2402,10 @@
if (adapter.getShowWallpaper()) {
mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
+ if (thumbnailAdapter != null) {
+ mThumbnail.startAnimation(
+ getPendingTransaction(), thumbnailAdapter, !isVisible());
+ }
}
} else {
cancelAnimation();
@@ -2429,6 +2541,17 @@
final DisplayContent dc = getDisplayContent();
dc.assignStackOrdering();
+
+ if (leash == mTransitChangeLeash) {
+ // This is a temporary state so skip any animation notifications
+ return;
+ } else if (mTransitChangeLeash != null) {
+ // unparent mTransitChangeLeash for clean-up
+ t.hide(mTransitChangeLeash);
+ t.reparent(mTransitChangeLeash, null);
+ mTransitChangeLeash = null;
+ }
+
if (mAnimatingAppWindowTokenRegistry != null) {
mAnimatingAppWindowTokenRegistry.notifyStarting(this);
}
@@ -2487,6 +2610,12 @@
+ " okToAnimate=" + okToAnimate()
+ " startingDisplayed=" + startingDisplayed);
+ // clean up thumbnail window
+ if (mThumbnail != null) {
+ mThumbnail.destroy();
+ mThumbnail = null;
+ }
+
// WindowState.onExitAnimationDone might modify the children list, so make a copy and then
// traverse the copy.
final ArrayList<WindowState> children = new ArrayList<>(mChildren);
@@ -2518,14 +2647,30 @@
@Override
void cancelAnimation() {
- super.cancelAnimation();
+ cancelAnimationOnly();
clearThumbnail();
+ if (mTransitChangeLeash != null) {
+ getPendingTransaction().hide(mTransitChangeLeash);
+ getPendingTransaction().reparent(mTransitChangeLeash, null);
+ mTransitChangeLeash = null;
+ }
+ }
+
+ /**
+ * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
+ * or interim leashes.
+ * <p>
+ * Used when canceling in preparation for starting a new animation.
+ */
+ void cancelAnimationOnly() {
+ super.cancelAnimation();
}
boolean isWaitingForTransitionStart() {
return getDisplayContent().mAppTransition.isTransitionSet()
&& (getDisplayContent().mOpeningApps.contains(this)
- || getDisplayContent().mClosingApps.contains(this));
+ || getDisplayContent().mClosingApps.contains(this)
+ || getDisplayContent().mChangingApps.contains(this));
}
public int getTransit() {
@@ -2835,6 +2980,7 @@
void removeFromPendingTransition() {
if (isWaitingForTransitionStart() && mDisplayContent != null) {
mDisplayContent.mOpeningApps.remove(this);
+ mDisplayContent.mChangingApps.remove(this);
mDisplayContent.mClosingApps.remove(this);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8fefd35..8f976e7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -250,6 +250,7 @@
final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+ final ArraySet<AppWindowToken> mChangingApps = new ArraySet<>();
final UnknownAppVisibilityController mUnknownAppVisibilityController;
BoundsAnimationController mBoundsAnimationController;
@@ -2454,6 +2455,7 @@
// Clear all transitions & screen frozen states when removing display.
mOpeningApps.clear();
mClosingApps.clear();
+ mChangingApps.clear();
mUnknownAppVisibilityController.clear();
mAppTransition.removeAppTransitionTimeoutCallbacks();
handleAnimatingStoppedAndTransition();
@@ -3284,7 +3286,7 @@
}
}
- if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+ if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty() || !mChangingApps.isEmpty()) {
pw.println();
if (mOpeningApps.size() > 0) {
pw.print(" mOpeningApps="); pw.println(mOpeningApps);
@@ -3292,6 +3294,9 @@
if (mClosingApps.size() > 0) {
pw.print(" mClosingApps="); pw.println(mClosingApps);
}
+ if (mChangingApps.size() > 0) {
+ pw.print(" mChangingApps="); pw.println(mChangingApps);
+ }
}
mUnknownAppVisibilityController.dump(pw, " ");
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 632db38..3c5d911 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -46,7 +46,6 @@
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
-import android.view.animation.Animation;
import com.android.server.AnimationThread;
import com.android.server.policy.WindowManagerPolicy;
@@ -70,8 +69,7 @@
private boolean mDisableWallpaperTouchEvents;
private final Rect mTmpRect = new Rect();
- private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer =
- new UpdateInputForAllWindowsConsumer();
+ private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
private final int mDisplayId;
private final DisplayContent mDisplayContent;
@@ -165,6 +163,7 @@
mDisplayId = displayId;
mInputTransaction = mDisplayContent.getPendingTransaction();
mHandler = AnimationThread.getHandler();
+ mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
}
void onDisplayRemoved() {
@@ -407,6 +406,9 @@
boolean inDrag;
WallpaperController wallpaperController;
+ // An invalid window handle that tells SurfaceFlinger not update the input info.
+ final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -445,6 +447,10 @@
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
if (inputChannel == null || inputWindowHandle == null || w.mRemoved
|| w.cantReceiveTouchInput()) {
+ if (w.mWinAnimator.hasSurface()) {
+ mInputTransaction.setInputWindowInfo(
+ w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+ }
// Skip this window because it cannot possibly receive input.
return;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index fb82cb0..105ff06 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -624,7 +624,7 @@
mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
!topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
insets, mTask.getPrefixOrderIndex(), mPosition, mBounds,
- mTask.getWindowConfiguration(), mIsRecentTaskInvisible);
+ mTask.getWindowConfiguration(), mIsRecentTaskInvisible, null, null);
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f5acdcc..f760b39 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -57,7 +57,7 @@
private final WindowManagerService mService;
private final RemoteAnimationAdapter mRemoteAnimationAdapter;
- private final ArrayList<RemoteAnimationAdapterWrapper> mPendingAnimations = new ArrayList<>();
+ private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
private final Rect mTmpRect = new Rect();
private final Handler mHandler;
private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -74,21 +74,22 @@
}
/**
- * Creates an animation for each individual {@link AppWindowToken}.
+ * Creates an animation record for each individual {@link AppWindowToken}.
*
* @param appWindowToken The app to animate.
* @param position The position app bounds, in screen coordinates.
- * @param stackBounds The stack bounds of the app.
- * @return The adapter to be run on the app.
+ * @param stackBounds The stack bounds of the app relative to position.
+ * @param startBounds The stack bounds before the transition, in screen coordinates
+ * @return The record representing animation(s) to run on the app.
*/
- AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
- Rect stackBounds) {
+ RemoteAnimationRecord createRemoteAnimationRecord(AppWindowToken appWindowToken,
+ Point position, Rect stackBounds, Rect startBounds) {
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token="
+ appWindowToken);
- final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
- appWindowToken, position, stackBounds);
- mPendingAnimations.add(adapter);
- return adapter;
+ final RemoteAnimationRecord adapters =
+ new RemoteAnimationRecord(appWindowToken, position, stackBounds, startBounds);
+ mPendingAnimations.add(adapters);
+ return adapters;
}
/**
@@ -148,7 +149,7 @@
final StringWriter sw = new StringWriter();
final FastPrintWriter pw = new FastPrintWriter(sw);
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- mPendingAnimations.get(i).dump(pw, "");
+ mPendingAnimations.get(i).mAdapter.dump(pw, "");
}
pw.close();
Slog.i(TAG, sw.toString());
@@ -158,19 +159,26 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationAdapterWrapper wrapper = mPendingAnimations.get(i);
- final RemoteAnimationTarget target = wrapper.createRemoteAppAnimation();
+ final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
+ final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
if (target != null) {
- if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken);
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrappers.mAppWindowToken);
targets.add(target);
} else {
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token="
- + wrapper.mAppWindowToken);
+ + wrappers.mAppWindowToken);
// We can't really start an animation but we still need to make sure to finish the
// pending animation that was started by SurfaceAnimator
- if (wrapper.mCapturedFinishCallback != null) {
- wrapper.mCapturedFinishCallback.onAnimationFinished(wrapper);
+ if (wrappers.mAdapter != null
+ && wrappers.mAdapter.mCapturedFinishCallback != null) {
+ wrappers.mAdapter.mCapturedFinishCallback
+ .onAnimationFinished(wrappers.mAdapter);
+ }
+ if (wrappers.mThumbnailAdapter != null
+ && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
+ wrappers.mThumbnailAdapter.mCapturedFinishCallback
+ .onAnimationFinished(wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
}
@@ -190,9 +198,16 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG,
"onAnimationFinished(): Notify animation finished:");
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
- final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
- adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
- if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapter.mAppWindowToken);
+ final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
+ if (adapters.mAdapter != null) {
+ adapters.mAdapter.mCapturedFinishCallback
+ .onAnimationFinished(adapters.mAdapter);
+ }
+ if (adapters.mThumbnailAdapter != null) {
+ adapters.mThumbnailAdapter.mCapturedFinishCallback
+ .onAnimationFinished(adapters.mThumbnailAdapter);
+ }
+ if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
}
} catch (Exception e) {
Slog.e(TAG, "Failed to finish remote animation", e);
@@ -282,47 +297,84 @@
}
};
- private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+ /**
+ * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
+ * potentially, multiple animating surfaces (AdapterWrappers) associated with one
+ * Window/Transition. For example, a change transition has an adapter controller for the
+ * main window and an adapter controlling the start-state snapshot.
+ * <p>
+ * This can be thought of as a bridge between the information that the remote animator sees (via
+ * {@link RemoteAnimationTarget}) and what the server sees (the
+ * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
+ */
+ public class RemoteAnimationRecord {
+ RemoteAnimationAdapterWrapper mAdapter;
+ RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
+ RemoteAnimationTarget mTarget;
+ final AppWindowToken mAppWindowToken;
+ final Rect mStartBounds;
- private final AppWindowToken mAppWindowToken;
- private SurfaceControl mCapturedLeash;
- private OnAnimationFinishedCallback mCapturedFinishCallback;
- private final Point mPosition = new Point();
- private final Rect mStackBounds = new Rect();
- private RemoteAnimationTarget mTarget;
-
- RemoteAnimationAdapterWrapper(AppWindowToken appWindowToken, Point position,
- Rect stackBounds) {
+ RemoteAnimationRecord(AppWindowToken appWindowToken, Point endPos, Rect endBounds,
+ Rect startBounds) {
mAppWindowToken = appWindowToken;
- mPosition.set(position.x, position.y);
- mStackBounds.set(stackBounds);
+ mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
+ if (startBounds != null) {
+ mStartBounds = new Rect(startBounds);
+ mTmpRect.set(startBounds);
+ mTmpRect.offsetTo(0, 0);
+ mThumbnailAdapter =
+ new RemoteAnimationAdapterWrapper(this, new Point(0, 0), mTmpRect);
+ } else {
+ mStartBounds = null;
+ }
}
- RemoteAnimationTarget createRemoteAppAnimation() {
+ RemoteAnimationTarget createRemoteAnimationTarget() {
final Task task = mAppWindowToken.getTask();
final WindowState mainWindow = mAppWindowToken.findMainWindow();
- if (task == null || mainWindow == null || mCapturedFinishCallback == null
- || mCapturedLeash == null) {
+ if (task == null || mainWindow == null || mAdapter == null
+ || mAdapter.mCapturedFinishCallback == null
+ || mAdapter.mCapturedLeash == null) {
return null;
}
final Rect insets = new Rect();
mainWindow.getContentInsets(insets);
InsetUtils.addInsets(insets, mAppWindowToken.getLetterboxInsets());
mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
- mCapturedLeash, !mAppWindowToken.fillsParent(),
+ mAdapter.mCapturedLeash, !mAppWindowToken.fillsParent(),
mainWindow.mWinAnimator.mLastClipRect, insets,
- mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds,
- task.getWindowConfiguration(), false /*isNotInRecents*/);
+ mAppWindowToken.getPrefixOrderIndex(), mAdapter.mPosition,
+ mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
+ mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
+ mStartBounds);
return mTarget;
}
private int getMode() {
- if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) {
+ final DisplayContent dc = mAppWindowToken.getDisplayContent();
+ if (dc.mOpeningApps.contains(mAppWindowToken)) {
return RemoteAnimationTarget.MODE_OPENING;
+ } else if (dc.mChangingApps.contains(mAppWindowToken)) {
+ return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
}
}
+ }
+
+ private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+ private final RemoteAnimationRecord mRecord;
+ SurfaceControl mCapturedLeash;
+ private OnAnimationFinishedCallback mCapturedFinishCallback;
+ private final Point mPosition = new Point();
+ private final Rect mStackBounds = new Rect();
+
+ RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
+ Rect stackBounds) {
+ mRecord = record;
+ mPosition.set(position.x, position.y);
+ mStackBounds.set(stackBounds);
+ }
@Override
public boolean getShowWallpaper() {
@@ -340,7 +392,7 @@
if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
// Restore z-layering, position and stack crop until client has a chance to modify it.
- t.setLayer(animationLeash, mAppWindowToken.getPrefixOrderIndex());
+ t.setLayer(animationLeash, mRecord.mAppWindowToken.getPrefixOrderIndex());
t.setPosition(animationLeash, mPosition.x, mPosition.y);
mTmpRect.set(mStackBounds);
mTmpRect.offsetTo(0, 0);
@@ -351,7 +403,14 @@
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- mPendingAnimations.remove(this);
+ if (mRecord.mAdapter == this) {
+ mRecord.mAdapter = null;
+ } else {
+ mRecord.mThumbnailAdapter = null;
+ }
+ if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
+ mPendingAnimations.remove(mRecord);
+ }
if (mPendingAnimations.isEmpty()) {
mHandler.removeCallbacks(mTimeoutRunnable);
releaseFinishedCallback();
@@ -373,10 +432,10 @@
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("token="); pw.println(mAppWindowToken);
- if (mTarget != null) {
+ pw.print(prefix); pw.print("token="); pw.println(mRecord.mAppWindowToken);
+ if (mRecord.mTarget != null) {
pw.print(prefix); pw.println("Target:");
- mTarget.dump(pw, prefix + " ");
+ mRecord.mTarget.dump(pw, prefix + " ");
} else {
pw.print(prefix); pw.println("Target: null");
}
@@ -385,8 +444,8 @@
@Override
public void writeToProto(ProtoOutputStream proto) {
final long token = proto.start(REMOTE);
- if (mTarget != null) {
- mTarget.writeToProto(proto, TARGET);
+ if (mRecord.mTarget != null) {
+ mRecord.mTarget.writeToProto(proto, TARGET);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
index b4de75b..6db606d 100644
--- a/services/core/java/com/android/server/wm/StatusBarController.java
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -61,9 +61,8 @@
}
@Override
- public int onAppTransitionStartingLocked(int transit, IBinder openToken,
- IBinder closeToken, long duration, long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarInternal();
if (statusBar != null && mWin != null) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 15239c7..c15afc5 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -661,7 +661,8 @@
* Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
* the opening apps should be a wallpaper target.
*/
- void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps) {
+ void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps,
+ ArraySet<AppWindowToken> changingApps) {
boolean adjust = false;
if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
adjust = true;
@@ -673,6 +674,15 @@
break;
}
}
+ if (!adjust) {
+ for (int i = changingApps.size() - 1; i >= 0; --i) {
+ final AppWindowToken token = changingApps.valueAt(i);
+ if (token.windowsCanBeWallpaperTarget()) {
+ adjust = true;
+ break;
+ }
+ }
+ }
}
if (adjust) {
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
new file mode 100644
index 0000000..7dd7c4f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+
+import java.io.PrintWriter;
+
+/**
+ * Animation spec for changing window animations.
+ */
+public class WindowChangeAnimationSpec implements AnimationSpec {
+
+ private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
+ private final boolean mIsAppAnimation;
+ private final Rect mStartBounds;
+ private final Rect mEndBounds;
+ private final Rect mTmpRect = new Rect();
+
+ private Animation mAnimation;
+ private final boolean mIsThumbnail;
+
+ public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
+ long duration, boolean isAppAnimation, boolean isThumbnail) {
+ mStartBounds = new Rect(startBounds);
+ mEndBounds = new Rect(endBounds);
+ mIsAppAnimation = isAppAnimation;
+ mIsThumbnail = isThumbnail;
+ createBoundsInterpolator(duration, displayInfo);
+ }
+
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public int getBackgroundColor() {
+ return 0;
+ }
+
+ @Override
+ public long getDuration() {
+ return mAnimation.getDuration();
+ }
+
+ /**
+ * This animator behaves slightly differently depending on whether the window is growing
+ * or shrinking:
+ * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
+ * snapshot.
+ * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
+ * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
+ * place.
+ * @param duration
+ * @param displayInfo
+ */
+ private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
+ boolean growing = mEndBounds.width() - mStartBounds.width()
+ + mEndBounds.height() - mStartBounds.height() >= 0;
+ float scalePart = 0.7f;
+ long scalePeriod = (long) (duration * scalePart);
+ float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
+ + (1.f - scalePart);
+ float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
+ + (1.f - scalePart);
+ if (mIsThumbnail) {
+ AnimationSet animSet = new AnimationSet(true);
+ Animation anim = new AlphaAnimation(1.f, 0.f);
+ anim.setDuration(scalePeriod);
+ if (!growing) {
+ anim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(anim);
+ float endScaleX = 1.f / startScaleX;
+ float endScaleY = 1.f / startScaleY;
+ anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
+ anim.setDuration(duration);
+ animSet.addAnimation(anim);
+ mAnimation = animSet;
+ mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+ mEndBounds.width(), mEndBounds.height());
+ } else {
+ AnimationSet animSet = new AnimationSet(true);
+ final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
+ scaleAnim.setDuration(scalePeriod);
+ if (!growing) {
+ scaleAnim.setStartOffset(duration - scalePeriod);
+ }
+ animSet.addAnimation(scaleAnim);
+ final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
+ mEndBounds.left, mStartBounds.top, mEndBounds.top);
+ translateAnim.setDuration(duration);
+ animSet.addAnimation(translateAnim);
+ Rect startClip = new Rect(mStartBounds);
+ Rect endClip = new Rect(mEndBounds);
+ startClip.offsetTo(0, 0);
+ endClip.offsetTo(0, 0);
+ final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+ clipAnim.setDuration(duration);
+ animSet.addAnimation(clipAnim);
+ mAnimation = animSet;
+ mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
+ displayInfo.appWidth, displayInfo.appHeight);
+ }
+ }
+
+ @Override
+ public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
+ final TmpValues tmp = mThreadLocalTmps.get();
+ if (mIsThumbnail) {
+ mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+ t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
+ t.setAlpha(leash, tmp.mTransformation.getAlpha());
+ } else {
+ mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
+ final Matrix matrix = tmp.mTransformation.getMatrix();
+ t.setMatrix(leash, matrix, tmp.mFloats);
+
+ // The following applies an inverse scale to the clip-rect so that it crops "after" the
+ // scale instead of before.
+ tmp.mVecs[1] = tmp.mVecs[2] = 0;
+ tmp.mVecs[0] = tmp.mVecs[3] = 1;
+ matrix.mapVectors(tmp.mVecs);
+ tmp.mVecs[0] = 1.f / tmp.mVecs[0];
+ tmp.mVecs[3] = 1.f / tmp.mVecs[3];
+ final Rect clipRect = tmp.mTransformation.getClipRect();
+ mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
+ mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
+ mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
+ mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
+ t.setWindowCrop(leash, mTmpRect);
+ }
+ }
+
+ @Override
+ public long calculateStatusBarTransitionStartTime() {
+ long uptime = SystemClock.uptimeMillis();
+ return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
+ - STATUS_BAR_TRANSITION_DURATION);
+ }
+
+ @Override
+ public boolean canSkipFirstFrame() {
+ return false;
+ }
+
+ @Override
+ public boolean needsEarlyWakeup() {
+ return mIsAppAnimation;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println(mAnimation.getDuration());
+ }
+
+ @Override
+ public void writeToProtoInner(ProtoOutputStream proto) {
+ final long token = proto.start(WINDOW);
+ proto.write(ANIMATION, mAnimation.toString());
+ proto.end(token);
+ }
+
+ private static class TmpValues {
+ final Transformation mTransformation = new Transformation();
+ final float[] mFloats = new float[9];
+ final float[] mVecs = new float[4];
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 5267e7e..e204697 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -118,8 +118,6 @@
*
* @param transit transition type indicating what kind of transition gets run, must be one
* of AppTransition.TRANSIT_* values
- * @param openToken the token for the opening app
- * @param closeToken the token for the closing app
* @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
@@ -131,8 +129,8 @@
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
- long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(int transit, long duration,
+ long statusBarAnimationStartTime, long statusBarAnimationDuration) {
return 0;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 90c9cc2..ff0b0d6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -782,7 +782,7 @@
}
sp<InputWindowHandle> windowHandle =
- android_server_InputWindowHandle_getHandle(env, windowHandleObj);
+ android_view_InputWindowHandle_getHandle(env, windowHandleObj);
if (windowHandle != nullptr) {
windowHandles.push(windowHandle);
}
@@ -822,7 +822,7 @@
void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
jobject applicationHandleObj) {
sp<InputApplicationHandle> applicationHandle =
- android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
+ android_view_InputApplicationHandle_getHandle(env, applicationHandleObj);
mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle);
}
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 918f57e..6e31aed 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -26,8 +26,6 @@
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
-int register_android_server_InputApplicationHandle(JNIEnv* env);
-int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
@@ -74,8 +72,6 @@
register_android_server_broadcastradio_Tuner(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
- register_android_server_InputApplicationHandle(env);
- register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index d8225b3..2bf6f35 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -108,12 +108,7 @@
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
@Override
- public void addCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- }
-
- @Override
- public boolean removeCrossProfileCalendarPackage(ComponentName admin, String packageName) {
- return false;
+ public void setCrossProfileCalendarPackages(ComponentName admin, List<String> packageNames) {
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 54053a8..23c68b2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -949,8 +949,8 @@
"metered_data_disabled_packages";
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES =
"cross-profile-calendar-packages";
- private static final String TAG_PACKAGE = "package";
-
+ private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
+ "cross-profile-calendar-packages-null";
DeviceAdminInfo info;
@@ -1072,7 +1072,9 @@
String endUserSessionMessage = null;
// The whitelist of packages that can access cross profile calendar APIs.
- final Set<String> mCrossProfileCalendarPackages = new ArraySet<>();
+ // This whitelist should be in default an empty list, which indicates that no package
+ // is whitelisted.
+ List<String> mCrossProfileCalendarPackages = Collections.emptyList();
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
@@ -1343,11 +1345,12 @@
out.text(endUserSessionMessage);
out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
}
- if (!mCrossProfileCalendarPackages.isEmpty()) {
- out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
- writeAttributeValuesToXml(
- out, TAG_PACKAGE, mCrossProfileCalendarPackages);
- out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES);
+ if (mCrossProfileCalendarPackages == null) {
+ out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ out.endTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
+ } else {
+ writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
+ mCrossProfileCalendarPackages);
}
}
@@ -1542,8 +1545,9 @@
Log.w(LOG_TAG, "Missing text when loading end session message");
}
} else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES.equals(tag)) {
- readAttributeValues(
- parser, TAG_PACKAGE, mCrossProfileCalendarPackages);
+ mCrossProfileCalendarPackages = readPackageList(parser, tag);
+ } else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
+ mCrossProfileCalendarPackages = null;
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1759,8 +1763,10 @@
pw.print(prefix); pw.println("parentAdmin:");
parentAdmin.dump(prefix + " ", pw);
}
- pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
- pw.println(mCrossProfileCalendarPackages);
+ if (mCrossProfileCalendarPackages != null) {
+ pw.print(prefix); pw.print("mCrossProfileCalendarPackages=");
+ pw.println(mCrossProfileCalendarPackages);
+ }
}
}
@@ -13988,55 +13994,27 @@
}
@Override
- public void addCrossProfileCalendarPackage(ComponentName who, String packageName) {
+ public void setCrossProfileCalendarPackages(ComponentName who, List<String> packageNames) {
if (!mHasFeature) {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (admin.mCrossProfileCalendarPackages.add(packageName)) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
+ admin.mCrossProfileCalendarPackages = packageNames;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALENDAR_PACKAGES)
.setAdmin(who)
- .setStrings(packageName)
+ .setStrings(packageNames == null ? null
+ : packageNames.toArray(new String[packageNames.size()]))
.write();
}
@Override
- public boolean removeCrossProfileCalendarPackage(ComponentName who, String packageName) {
- if (!mHasFeature) {
- return false;
- }
- Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkStringNotEmpty(packageName, "Package name is null or empty");
-
- boolean isRemoved = false;
- synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- isRemoved = admin.mCrossProfileCalendarPackages.remove(packageName);
- if (isRemoved) {
- saveSettingsLocked(mInjector.userHandleGetCallingUserId());
- }
- }
- if (isRemoved) {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
- .setAdmin(who)
- .setStrings(packageName)
- .write();
- }
- return isRemoved;
- }
-
- @Override
public List<String> getCrossProfileCalendarPackages(ComponentName who) {
if (!mHasFeature) {
return Collections.emptyList();
@@ -14046,7 +14024,7 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
@@ -14062,6 +14040,9 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
+ if (admin.mCrossProfileCalendarPackages == null) {
+ return true;
+ }
return admin.mCrossProfileCalendarPackages.contains(packageName);
}
}
@@ -14077,7 +14058,7 @@
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
if (admin != null) {
- return new ArrayList<String>(admin.mCrossProfileCalendarPackages);
+ return admin.mCrossProfileCalendarPackages;
}
}
return Collections.emptyList();
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6a153d5..6386b3b3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -28,14 +28,18 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
-import static com.android.server.AlarmManagerService.Constants
- .KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED;
import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.AlarmManagerService.WORKING_INDEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -58,6 +62,7 @@
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
@@ -65,7 +70,6 @@
import android.util.Log;
import android.util.SparseArray;
-import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.annotations.GuardedBy;
@@ -83,7 +87,6 @@
import java.util.ArrayList;
@Presubmit
-@SmallTest
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
@@ -91,7 +94,9 @@
private static final int SYSTEM_UI_UID = 123456789;
private static final int TEST_CALLING_UID = 12345;
+ private long mAppStandbyWindow;
private AlarmManagerService mService;
+ private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
@Mock
private ContentResolver mMockResolver;
@Mock
@@ -229,16 +234,23 @@
mService = new AlarmManagerService(mMockContext, mInjector);
spyOn(mService);
doNothing().when(mService).publishBinderService(any(), any());
- mService.onStart();
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- spyOn(mService.mHandler);
- assertEquals(0, mService.mConstants.MIN_FUTURITY);
+ mService.onStart();
+ spyOn(mService.mHandler);
assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID);
assertEquals(mService.mClockReceiver, mClockReceiver);
assertEquals(mService.mWakeLock, mWakeLock);
verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(),
isNull());
+
+ // Other boot phases don't matter
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ assertEquals(0, mService.mConstants.MIN_FUTURITY);
+ mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
+ ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor =
+ ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
+ verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
+ mAppStandbyListener = captor.getValue();
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -254,6 +266,28 @@
return mockPi;
}
+ /**
+ * Careful while calling as this will replace any existing settings for the calling test.
+ */
+ private void setQuotasEnabled(boolean enabled) {
+ final StringBuilder constantsBuilder = new StringBuilder();
+ constantsBuilder.append(KEY_MIN_FUTURITY);
+ constantsBuilder.append("=0,");
+ // Capping active and working quotas to make testing feasible.
+ constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ constantsBuilder.append("=8,");
+ constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+ constantsBuilder.append("=5,");
+ if (!enabled) {
+ constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED);
+ constantsBuilder.append("=false,");
+ }
+ doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
+ Settings.Global.ALARM_MANAGER_CONSTANTS));
+ mService.mConstants.onChange(false, null);
+ assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled);
+ }
+
@Test
public void testSingleAlarmSet() {
final long triggerTime = mNowElapsedTest + 5000;
@@ -346,6 +380,7 @@
@Test
public void testStandbyBucketDelay_workingSet() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -366,6 +401,7 @@
@Test
public void testStandbyBucketDelay_frequent() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -385,6 +421,7 @@
@Test
public void testStandbyBucketDelay_rare() throws Exception {
+ setQuotasEnabled(false);
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent());
setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent());
assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed());
@@ -402,6 +439,253 @@
assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed());
}
+ private void testQuotasDeferralOnSet(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ // This one should get deferred on set
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+ getNewMockPendingIntent());
+ final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ }
+ // This one should get deferred after the latest alarm expires
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10,
+ getNewMockPendingIntent());
+ for (int i = 0; i < quota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ private void testQuotasNoDeferral(int standbyBucket) throws Exception {
+ final int quota = mService.getQuotaForBucketLocked(standbyBucket);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(standbyBucket);
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < quota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i,
+ getNewMockPendingIntent());
+ }
+ // This delivery time maintains the quota invariant. Should not be deferred.
+ final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5;
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent());
+ for (int i = 0; i < quota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void testActiveQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testActiveQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testActiveQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE);
+ }
+
+ @Test
+ public void testWorkingQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testWorkingQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testWorkingQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET);
+ }
+
+ @Test
+ public void testFrequentQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testFrequentQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testFrequentQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT);
+ }
+
+ @Test
+ public void testRareQuota_deferredOnSet() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnSet(STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testRareQuota_deferredOnExpiration() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE);
+ }
+
+ @Test
+ public void testRareQuota_notDeferred() throws Exception {
+ setQuotasEnabled(true);
+ testQuotasNoDeferral(STANDBY_BUCKET_RARE);
+ }
+
+ private void sendAndHandleBucketChanged(int bucket) {
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(bucket);
+ // Stubbing the handler call to simulate it synchronously here.
+ doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+ mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE,
+ UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0);
+ final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+ final Message lastMessage = messageCaptor.getValue();
+ assertEquals("Unexpected message send to handler", lastMessage.what,
+ APP_STANDBY_BUCKET_CHANGED);
+ mService.mHandler.handleMessage(lastMessage);
+ }
+
+ @Test
+ public void testQuotaDowngrade() throws Exception {
+ setQuotasEnabled(true);
+ final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < workingQuota; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ }
+ // No deferrals now.
+ for (int i = 0; i < workingQuota - 1; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // The next upcoming alarm in queue should also be set as expected.
+ assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed());
+ // Downgrading the bucket now
+ sendAndHandleBucketChanged(STANDBY_BUCKET_RARE);
+ final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE);
+ // The last alarm should now be deferred.
+ final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota)
+ + mAppStandbyWindow + 1;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void testQuotaUpgrade() throws Exception {
+ setQuotasEnabled(true);
+ final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ for (int i = 0; i < frequentQuota + 1; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ if (i < frequentQuota) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+ }
+ }
+ // The last alarm should be deferred due to exceeding the quota
+ final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow;
+ assertEquals(deferredTrigger, mTestTimer.getElapsed());
+
+ // Upgrading the bucket now
+ sendAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE);
+ // The last alarm should now be rescheduled to go as per original expectations
+ final long originalTrigger = firstTrigger + frequentQuota;
+ assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
+ }
+
+ private void sendAndHandleParoleChanged(boolean parole) {
+ // Stubbing the handler call to simulate it synchronously here.
+ doReturn(true).when(mService.mHandler).sendMessage(any(Message.class));
+ mAppStandbyListener.onParoleStateChanged(parole);
+ final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
+ final Message lastMessage = messageCaptor.getValue();
+ assertEquals("Unexpected message send to handler", lastMessage.what,
+ APP_STANDBY_PAROLE_CHANGED);
+ mService.mHandler.handleMessage(lastMessage);
+ }
+
+ @Test
+ public void testParole() throws Exception {
+ setQuotasEnabled(true);
+ final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
+ when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
+ anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET);
+
+ final long firstTrigger = mNowElapsedTest + 10;
+ final int totalAlarms = workingQuota + 10;
+ for (int i = 0; i < totalAlarms; i++) {
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
+ }
+ // Use up the quota, no deferrals expected.
+ for (int i = 0; i < workingQuota; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // Any subsequent alarms in queue should all be deferred
+ assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed());
+ // Paroling now
+ sendAndHandleParoleChanged(true);
+
+ // Subsequent alarms should now go off as per original expectations.
+ for (int i = 0; i < 5; i++) {
+ mNowElapsedTest = mTestTimer.getElapsed();
+ assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest);
+ mTestTimer.expire();
+ }
+ // Come out of parole
+ sendAndHandleParoleChanged(false);
+
+ // Subsequent alarms should again get deferred
+ final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow;
+ assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
+ }
+
@Test
public void testAlarmRestrictedInBatterSaver() throws Exception {
final ArgumentCaptor<AppStateTracker.Listener> listenerArgumentCaptor =
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
similarity index 73%
rename from services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
rename to services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
index f31ca55..5009d64 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -19,51 +19,41 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.app.NotificationManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings.Global;
-import android.test.mock.MockContext;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.google.common.base.Objects;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
+import java.util.Objects;
/**
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ * atest com.android.server.power.batterysaver.BatterySaverStateMachineTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatterySaverStateMachineTest {
- private MyMockContext mMockContext;
+ private Context mMockContext;
private ContentResolver mMockContextResolver;
private BatterySaverController mMockBatterySaverController;
+ private NotificationManager mMockNotificationManager;
private Device mDevice;
private TestableBatterySaverStateMachine mTarget;
private Resources mMockResources;
- private class MyMockContext extends MockContext {
- @Override
- public ContentResolver getContentResolver() {
- return mMockContextResolver;
- }
-
- @Override
- public Resources getResources() {
- return mMockResources;
- }
- }
-
private DevicePersistedState mPersistedState;
private class DevicePersistedState {
@@ -116,6 +106,10 @@
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
mDevice.getLowPowerModeTriggerLevel(),
+ mPersistedState.global.getOrDefault(
+ Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0) != 0,
+ mPersistedState.global.getOrDefault(
+ Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90),
mPersistedState.global.getOrDefault(Global.AUTOMATIC_POWER_SAVER_MODE, 0),
mPersistedState.global.getOrDefault(
Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0,
@@ -137,13 +131,13 @@
* Test target class.
*/
private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
- public TestableBatterySaverStateMachine() {
+ TestableBatterySaverStateMachine() {
super(new Object(), mMockContext, mMockBatterySaverController);
}
@Override
protected void putGlobalSetting(String key, int value) {
- if (Objects.equal(mPersistedState.global.get(key), value)) {
+ if (Objects.equals(mPersistedState.global.get(key), value)) {
return;
}
mDevice.putGlobalSetting(key, value);
@@ -163,15 +157,25 @@
void runOnBgThreadLazy(Runnable r, int delayMillis) {
r.run();
}
+
+ @Override
+ void triggerDynamicModeNotification() {
+ // Do nothing
+ }
}
@Before
public void setUp() {
- mMockContext = new MyMockContext();
+ mMockContext = mock(Context.class);
mMockContextResolver = mock(ContentResolver.class);
mMockBatterySaverController = mock(BatterySaverController.class);
+ mMockNotificationManager = mock(NotificationManager.class);
mMockResources = mock(Resources.class);
+ doReturn(mMockContextResolver).when(mMockContext).getContentResolver();
+ doReturn(mMockResources).when(mMockContext).getResources();
+ doReturn(mMockNotificationManager).when(mMockContext)
+ .getSystemService(NotificationManager.class);
doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
.when(mMockBatterySaverController).enableBatterySaver(anyBoolean(), anyInt());
when(mMockBatterySaverController.isEnabled())
@@ -446,8 +450,9 @@
}
@Test
- public void testAutoBatterySaver_withSticky() {
+ public void testAutoBatterySaver_withSticky_withAutoOffDisabled() {
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0);
mTarget.setBatterySaverEnabledManually(true);
@@ -518,6 +523,197 @@
}
@Test
+ public void testAutoBatterySaver_withSticky_withAutoOffEnabled() {
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
+
+ // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even
+ // with battery level change above threshold.
+ mDevice.setBatteryLevel(100);
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(95);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Stays on.
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 2: User turns BS on manually above the threshold then charges device. BS
+ // shouldn't turn back on.
+ mDevice.setPowered(true);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(97);
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+ assertEquals(97, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 3: User turns BS on manually above the threshold. Device drains below
+ // threshold and then charged to below threshold. Sticky BS should activate.
+ mTarget.setBatterySaverEnabledManually(true);
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Scenario 4: User turns BS on manually above the threshold. Device drains below
+ // threshold and is eventually charged to above threshold. Sticky BS should turn off.
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 5: User turns BS on manually below threshold and charges to below threshold.
+ // Sticky BS should activate.
+ mDevice.setBatteryLevel(70);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 6: User turns BS on manually below threshold and eventually charges to above
+ // threshold. Sticky BS should turn off.
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(95);
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(95, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS
+ // shouldn't activate.
+ mTarget.setBatterySaverEnabledManually(true);
+ mPersistedState.batteryLevel = 93;
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(93, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 8: User turns BS on below threshold and then reboots device without charging.
+ // Sticky BS should activate.
+ mDevice.setBatteryLevel(75);
+ mTarget.setBatterySaverEnabledManually(true);
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(75, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(75, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 9: User turns BS on below threshold and then reboots device after charging
+ // above threshold. Sticky BS shouldn't activate.
+ mDevice.setBatteryLevel(80);
+ mTarget.setBatterySaverEnabledManually(true);
+ mPersistedState.batteryLevel = 100;
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel
+ // and then user enables manually above both thresholds, discharges below
+ // autoDisableLevel and then charges up to between autoDisableLevel and
+ // lowPowerModeTriggerLevel. Sticky BS shouldn't activate, but BS should still be on
+ // because the level is below lowPowerModeTriggerLevel.
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 75);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 60);
+ initDevice();
+
+ mDevice.setBatteryLevel(90);
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(65);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(65, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(65, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+ }
+
+ @Test
public void testAutoBatterySaver_withStickyDisabled() {
when(mMockResources.getBoolean(
com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
new file mode 100644
index 0000000..782dc3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER;
+
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
+import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInputFilterTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInputFilterTest {
+ private static int sNextDisplayId = DEFAULT_DISPLAY;
+ private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 1;
+ private static final float DEFAULT_X = 100f;
+ private static final float DEFAULT_Y = 100f;
+
+ private final SparseArray<EventStreamTransformation> mEventHandler = new SparseArray<>(0);
+ private final ArrayList<Display> mDisplayList = new ArrayList<>();
+ private final int mFeatures = FLAG_FEATURE_AUTOCLICK
+ | FLAG_FEATURE_TOUCH_EXPLORATION
+ | FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER
+ | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER
+ | FLAG_FEATURE_INJECT_MOTION_EVENTS
+ | FLAG_FEATURE_FILTER_KEY_EVENTS;
+
+ // The expected order of EventStreamTransformations.
+ private final Class[] mExpectedEventHandlerTypes =
+ {KeyboardInterceptor.class, MotionEventInjector.class,
+ MagnificationGestureHandler.class, TouchExplorer.class,
+ AutoclickController.class, AccessibilityInputFilter.class};
+
+ private MagnificationController mMockMagnificationController;
+ private AccessibilityManagerService mAms;
+ private AccessibilityInputFilter mA11yInputFilter;
+ private EventCaptor mCaptor1;
+ private EventCaptor mCaptor2;
+ private long mLastDownTime = Integer.MIN_VALUE;
+
+ private class EventCaptor implements EventStreamTransformation {
+ List<InputEvent> mEvents = new ArrayList<>();
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mEvents.add(event.copy());
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ mEvents.add(event.copy());
+ }
+
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ }
+
+ @Override
+ public EventStreamTransformation getNext() {
+ return null;
+ }
+
+ @Override
+ public void clearEvents(int inputSource) {
+ clear();
+ }
+
+ private void clear() {
+ mEvents.clear();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getContext();
+
+ setDisplayCount(1);
+ mAms = spy(new AccessibilityManagerService(context));
+ mMockMagnificationController = mock(MagnificationController.class);
+ mA11yInputFilter = new AccessibilityInputFilter(context, mAms, mEventHandler);
+ mA11yInputFilter.onInstalled();
+
+ when(mAms.getValidDisplayList()).thenReturn(mDisplayList);
+ when(mAms.getMagnificationController()).thenReturn(mMockMagnificationController);
+ }
+
+ @After
+ public void tearDown() {
+ mA11yInputFilter.onUninstalled();
+ }
+
+ @Test
+ public void testEventHandler_shouldChangeAfterSetUserAndEnabledFeatures() {
+ prepareLooper();
+
+ // Check if there is no mEventHandler when no feature is set.
+ assertEquals(0, mEventHandler.size());
+
+ // Check if mEventHandler is added/removed after setting a11y features.
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, 0);
+ assertEquals(0, mEventHandler.size());
+ }
+
+ @Test
+ public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+ prepareLooper();
+
+ // Check if there is only one mEventHandler when there is one default display.
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ // Check if it has correct numbers of mEventHandler for corresponding displays.
+ setDisplayCount(4);
+ mA11yInputFilter.onDisplayChanged();
+ assertEquals(4, mEventHandler.size());
+
+ setDisplayCount(2);
+ mA11yInputFilter.onDisplayChanged();
+ assertEquals(2, mEventHandler.size());
+ }
+
+ @Test
+ public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ // Check if mEventHandler for each display has correct order of the
+ // EventStreamTransformations.
+ EventStreamTransformation next = mEventHandler.get(DEFAULT_DISPLAY);
+ for (int i = 0; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+
+ next = mEventHandler.get(SECOND_DISPLAY);
+ // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+ // DEFAULT_DISPLAY.
+ for (int i = 1; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+ }
+
+ @Test
+ public void testInputEvent_shouldDispatchToCorrespondingEventHandlers() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ mCaptor1 = new EventCaptor();
+ mCaptor2 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+ mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+ // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+ assertEquals(1, mCaptor1.mEvents.size());
+ assertEquals(1, mCaptor2.mEvents.size());
+ }
+
+ @Test
+ public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+ prepareLooper();
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(1, mEventHandler.size());
+
+ mCaptor1 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ assertEquals(2, mCaptor1.mEvents.size());
+
+ // InputEvent with different input source should trigger clearEvents() for each
+ // EventStreamTransformation in EventHandler.
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
+ assertEquals(1, mCaptor1.mEvents.size());
+ }
+
+ private static void prepareLooper() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ private Display createStubDisplay(DisplayInfo displayInfo) {
+ final int displayId = sNextDisplayId++;
+ final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+ displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+ return display;
+ }
+
+ private void setDisplayCount(int count) {
+ sNextDisplayId = DEFAULT_DISPLAY;
+ mDisplayList.clear();
+ for (int i = 0; i < count; i++) {
+ mDisplayList.add(createStubDisplay(new DisplayInfo()));
+ }
+ }
+
+ private void send(InputEvent event) {
+ mA11yInputFilter.onInputEvent(event, /* policyFlags */ FLAG_PASS_TO_USER);
+ }
+
+ private MotionEvent downEvent(int displayId, int source) {
+ mLastDownTime = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(mLastDownTime, mLastDownTime,
+ MotionEvent.ACTION_DOWN, DEFAULT_X, DEFAULT_Y, 0);
+ ev.setDisplayId(displayId);
+ ev.setSource(source);
+ return ev;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0813e6fa..535198b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5239,6 +5239,45 @@
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
+ public void testCrossProfileCalendarPackages_initiallyEmpty() {
+ setAsProfileOwner(admin1);
+ final Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ }
+
+ public void testCrossProfileCalendarPackages_reopenDpms() {
+ setAsProfileOwner(admin1);
+ dpm.setCrossProfileCalendarPackages(admin1, null);
+ Set<String> packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertTrue(packages == null);
+
+ dpm.setCrossProfileCalendarPackages(admin1, Collections.emptySet());
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, Collections.emptySet());
+
+ final String dummyPackageName = "test";
+ final Set<String> testPackages = new ArraySet<String>(Arrays.asList(dummyPackageName));
+ dpm.setCrossProfileCalendarPackages(admin1, testPackages);
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ initializeDpms();
+ packages = dpm.getCrossProfileCalendarPackages(admin1);
+ assertCrossProfileCalendarPackagesEqual(packages, testPackages);
+ }
+
+ private void assertCrossProfileCalendarPackagesEqual(Set<String> expected, Set<String> actual) {
+ assertTrue(expected != null);
+ assertTrue(actual != null);
+ assertTrue(expected.containsAll(actual));
+ assertTrue(actual.containsAll(expected));
+ }
+
private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index bc1f798..6845f15 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -43,6 +43,7 @@
import android.app.ActivityManagerInternal;
import android.app.IUidObserver;
import android.app.Person;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -143,8 +144,10 @@
switch (name) {
case Context.USER_SERVICE:
return mMockUserManager;
+ case Context.DEVICE_POLICY_SERVICE:
+ return mMockDevicePolicyManager;
}
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
@Override
@@ -610,6 +613,7 @@
protected PackageManager mMockPackageManager;
protected PackageManagerInternal mMockPackageManagerInternal;
protected UserManager mMockUserManager;
+ protected DevicePolicyManager mMockDevicePolicyManager;
protected UserManagerInternal mMockUserManagerInternal;
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -750,6 +754,7 @@
mMockPackageManager = mock(PackageManager.class);
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockUserManager = mock(UserManager.class);
+ mMockDevicePolicyManager = mock(DevicePolicyManager.class);
mMockUserManagerInternal = mock(UserManagerInternal.class);
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index b348aee..5d69bbd 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -18,12 +18,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +67,7 @@
private static final long TIME_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_000L;
- private static final long TIME_1_MIN = 10 * 60_000L;
+ private static final long TIME_1_MIN = 1 * 60_000L;
private static final long MAX_OBSERVER_PER_UID = 10;
private static final long MIN_TIME_LIMIT = 4_000L;
@@ -128,6 +131,11 @@
}
@Override
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ @Override
protected long getMinTimeLimit() {
return MIN_TIME_LIMIT;
}
@@ -164,6 +172,16 @@
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** Verify app usage limit observer is added */
+ @Test
+ public void testAppUsageLimitObserver_AddObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveObserver() {
@@ -182,6 +200,15 @@
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveObserver() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Verify nothing happens when a nonexistent app usage observer is removed */
@Test
public void testAppUsageObserver_RemoveMissingObserver() {
@@ -218,6 +245,24 @@
assertFalse("Observer should not exist", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify nothing happens when a nonexistent app usage limit observer is removed */
+ @Test
+ public void testAppUsageLimitObserver_RemoveMissingObserver() {
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ try {
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ sw.write("Hit exception trying to remove nonexistent observer:\n");
+ sw.write(e.toString());
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ sw.write("\nTest Failed!");
+ fail(sw.toString());
+ }
+ assertFalse("Observer should not exist", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Re-adding an observer should result in only one copy */
@Test
public void testAppUsageObserver_ObserverReAdd() {
@@ -242,22 +287,39 @@
assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Re-adding an observer should result in only one copy */
+ @Test
+ public void testAppUsageLimitObserver_ObserverReAdd() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ assertTrue("Observer wasn't added",
+ getAppUsageLimitObserver(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeAppUsageLimitObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** Different type observers can be registered to the same observerId value */
@Test
public void testAllObservers_ExclusiveObserverIds() {
addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_10_MIN);
assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ assertTrue("Observer wasn't added", hasAppUsageLimitObserver(UID, OBS_ID1));
AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
OBS_ID1);
AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
OBS_ID1);
+ AppTimeLimitController.UsageGroup appUsageLimitGroup = getAppUsageLimitObserver(
+ UID, OBS_ID1);
// Verify data still intact
assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+ assertEquals(TIME_10_MIN, appUsageLimitGroup.getTimeLimitMs());
}
/** Verify that usage across different apps within a group are added up */
@@ -299,7 +361,7 @@
@Test
public void testUsageSessionObserver_Accumulation() throws Exception {
setTime(0L);
- addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_10_MIN);
startUsage(PKG_SOC1);
// Add 10 mins
setTime(TIME_10_MIN);
@@ -330,6 +392,41 @@
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that usage across different apps within a group are added up */
+ @Test
+ public void testAppUsageLimitObserver_Accumulation() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN * 2);
+ stopUsage(PKG_SOC1);
+
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN, timeRemaining);
+
+ setTime(TIME_30_MIN);
+
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ // Add a different package in the group
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that time limit does not get triggered due to a different app */
@Test
public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
@@ -355,6 +452,18 @@
}
+ /** Verify that time limit does not get triggered due to a different app */
+ @Test
+ public void testAppUsageLimitObserver_TimeoutOtherApp() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(6_000L);
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify the timeout message is delivered at the right time */
@Test
public void testAppUsageObserver_Timeout() throws Exception {
@@ -385,6 +494,19 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** Verify the timeout message is delivered at the right time */
+ @Test
+ public void testAppUsageLimitObserver_Timeout() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/** If an app was already running, make sure it is partially counted towards the time limit */
@Test
public void testAppUsageObserver_AlreadyRunning() throws Exception {
@@ -423,6 +545,25 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
}
+ /** If an app was already running, make sure it is partially counted towards the time limit */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunning() throws Exception {
+ setTime(TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ startUsage(PKG_GAME2);
+ setTime(TIME_30_MIN + TIME_30_MIN);
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID2));
+ }
+
/** If watched app is already running, verify the timeout callback happens at the right time */
@Test
public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
@@ -464,6 +605,24 @@
assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
+ /** If watched app is already running, verify the timeout callback happens at the right time */
+ @Test
+ public void testAppUsageLimitObserver_AlreadyRunningTimeout() throws Exception {
+ setTime(0);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ // 10 second time limit
+ addAppUsageLimitObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ setTime(TIME_10_MIN + 5_000L);
+ // Shouldn't call back in 6 seconds
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(TIME_10_MIN + 10_000L);
+ // Should call back by 11 seconds (6 earlier + 5 now)
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasAppUsageLimitObserver(UID, OBS_ID1));
+ }
+
/**
* Verify that App Time Limit Controller will limit the number of observerIds for app usage
* observers
@@ -525,6 +684,37 @@
assertTrue("Should have caused an IllegalStateException", receivedException);
}
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+ * limit observers
+ */
+ @Test
+ public void testAppUsageLimitObserver_MaxObserverLimit() throws Exception {
+ boolean receivedException = false;
+ int ANOTHER_UID = UID + 1;
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+ // Readding an observer should not cause an IllegalStateException
+ addAppUsageLimitObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ // Adding an observer for a different uid shouldn't cause an IllegalStateException
+ mController.addAppUsageLimitObserver(
+ ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+ try {
+ addAppUsageLimitObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+ } catch (IllegalStateException ise) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalStateException", receivedException);
+ }
+
/** Verify that addAppUsageObserver minimum time limit is one minute */
@Test
public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
@@ -553,6 +743,20 @@
assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
+ /** Verify that addAppUsageLimitObserver minimum time limit is one minute */
+ @Test
+ public void testAppUsageLimitObserver_MinimumTimeLimit() throws Exception {
+ boolean receivedException = false;
+ // adding an observer with a one minute time limit should not cause an exception
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+ try {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+ } catch (IllegalArgumentException iae) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalArgumentException", receivedException);
+ }
+
/** Verify that concurrent usage from multiple apps in the same group will counted correctly */
@Test
public void testAppUsageObserver_ConcurrentUsage() throws Exception {
@@ -599,6 +803,29 @@
assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testAppUsageLimitObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.UsageGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
/** Verify that a session will continue if usage starts again within the session threshold */
@Test
public void testUsageSessionObserver_ContinueSession() throws Exception {
@@ -737,6 +964,97 @@
assertFalse(hasAppUsageObserver(UID, OBS_ID1));
}
+ /** Verify app usage limit observer added correctly reports it being a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertTrue("Observer didn't correctly report being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports it being not a group limit */
+ @Test
+ public void testAppUsageLimitObserver_IsNotGroupLimit() {
+ addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertFalse("Observer didn't correctly report not being a group limit",
+ group.isGroupLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetTotalUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_30_MIN, group.getTotaUsageLimit());
+ }
+
+ /** Verify app usage limit observer added correctly reports its total usage limit */
+ @Test
+ public void testAppUsageLimitObserver_GetUsageRemaining() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
+ assertNotNull("Observer wasn't added", group);
+ assertEquals("Observer didn't correctly report total usage limit",
+ TIME_10_MIN * 2, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimit() {
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_10_MIN, group.getTotalUsageLimit());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2 + TIME_1_MIN);
+ stopUsage(PKG_GAME1);
+ // PKG_GAME1 is only in GROUP1 but since we're querying for PCK_SOC1 which is
+ // in both groups, GROUP1 should be returned since it has a smaller time remaining
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ TIME_1_MIN * 9, group.getUsageRemaining());
+ }
+
+ /** Verify the app usage limit observer with the smallest usage limit remaining is returned
+ * when querying the getAppUsageLimit API.
+ */
+ @Test
+ public void testAppUsageLimitObserver_GetAppUsageLimitAllUsed() {
+ setTime(0L);
+ addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageLimitObserver(OBS_ID2, GROUP_SOC, TIME_10_MIN);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+ // GROUP_SOC should be returned since it should be completely used up (0ms remaining)
+ UsageStatsManagerInternal.AppUsageLimitData group = getAppUsageLimit(PKG_SOC1);
+ assertEquals("Observer with the smallest usage limit remaining wasn't returned",
+ 0L, group.getUsageRemaining());
+ }
+
private void startUsage(String packageName) {
mController.noteUsageStart(packageName, USER_ID);
}
@@ -759,6 +1077,10 @@
null, null, USER_ID);
}
+ private void addAppUsageLimitObserver(int observerId, String[] packages, long timeLimit) {
+ mController.addAppUsageLimitObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ }
+
/** Is there still an app usage observer by that id */
private boolean hasAppUsageObserver(int uid, int observerId) {
return mController.getAppUsageGroup(uid, observerId) != null;
@@ -769,6 +1091,20 @@
return mController.getSessionUsageGroup(uid, observerId) != null;
}
+ /** Is there still an app usage limit observer by that id */
+ private boolean hasAppUsageLimitObserver(int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId) != null;
+ }
+
+ private AppTimeLimitController.AppUsageLimitGroup getAppUsageLimitObserver(
+ int uid, int observerId) {
+ return mController.getAppUsageLimitGroup(uid, observerId);
+ }
+
+ private UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(String packageName) {
+ return mController.getAppUsageLimit(packageName, UserHandle.of(USER_ID));
+ }
+
private void setTime(long time) {
mUptimeMillis = time;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 1c5391e..9ce5795 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -106,7 +106,7 @@
}
public void notifyTransitionStarting(int transit) {
- mListener.onAppTransitionStartingLocked(transit, null, null, 0, 0, 0);
+ mListener.onAppTransitionStartingLocked(transit, 0, 0, 0);
}
public void notifyTransitionFinished() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 413b6f4..9478be9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import android.graphics.Point;
@@ -43,6 +44,7 @@
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
+import com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import org.junit.Before;
@@ -60,8 +62,10 @@
public class RemoteAnimationControllerTest extends WindowTestsBase {
@Mock SurfaceControl mMockLeash;
+ @Mock SurfaceControl mMockThumbnailLeash;
@Mock Transaction mMockTransaction;
@Mock OnAnimationFinishedCallback mFinishedCallback;
+ @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback;
@Mock IRemoteAnimationRunner mMockRunner;
private RemoteAnimationAdapter mAdapter;
private RemoteAnimationController mController;
@@ -84,8 +88,8 @@
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mAppToken);
try {
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -117,8 +121,8 @@
@Test
public void testCancel() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -129,8 +133,8 @@
@Test
public void testTimeout() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -147,8 +151,8 @@
mWm.setAnimationScale(2, 5.0f);
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
"testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
@@ -176,8 +180,8 @@
@Test
public void testNotReallyStarted() {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null);
mController.goodToGo();
verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
}
@@ -186,10 +190,10 @@
public void testOneNotStarted() throws Exception {
final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
- mController.createAnimationAdapter(win1.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
- final AnimationAdapter adapter = mController.createAnimationAdapter(win2.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ mController.createRemoteAnimationRecord(win1.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null);
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
mController.goodToGo();
mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -205,8 +209,8 @@
@Test
public void testRemovedBeforeStarted() {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createAnimationAdapter(win.mAppToken,
- new Point(50, 100), new Rect(50, 100, 150, 150));
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+ new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
win.mAppToken.removeImmediately();
mController.goodToGo();
@@ -214,6 +218,49 @@
verify(mFinishedCallback).onAnimationFinished(eq(adapter));
}
+ @Test
+ public void testChange() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mChangingApps.add(win.mAppToken);
+ try {
+ final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
+ win.mAppToken, new Point(50, 100), new Rect(50, 100, 150, 150),
+ new Rect(0, 0, 200, 200));
+ assertNotNull(record.mThumbnailAdapter);
+ ((AnimationAdapter) record.mAdapter)
+ .startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+ ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
+ mMockTransaction, mThumbnailFinishedCallback);
+ mController.goodToGo();
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+ assertEquals(1, appsCaptor.getValue().length);
+ final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+ assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
+ assertEquals(new Point(50, 100), app.position);
+ assertEquals(new Rect(50, 100, 150, 150), app.sourceContainerBounds);
+ assertEquals(new Rect(0, 0, 200, 200), app.startBounds);
+ assertEquals(mMockLeash, app.leash);
+ assertEquals(mMockThumbnailLeash, app.startLeash);
+ assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
+ assertEquals(false, app.isTranslucent);
+ verify(mMockTransaction).setLayer(mMockLeash, app.prefixOrderIndex);
+ verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
+ verify(mMockTransaction).setWindowCrop(mMockLeash, new Rect(0, 0, 200, 200));
+ verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
+
+ finishedCaptor.getValue().onAnimationFinished();
+ verify(mFinishedCallback).onAnimationFinished(eq(record.mAdapter));
+ verify(mThumbnailFinishedCallback).onAnimationFinished(eq(record.mThumbnailAdapter));
+ } finally {
+ mDisplayContent.mChangingApps.clear();
+ }
+ }
+
private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
verify(binder, atLeast(0)).asBinder();
verifyNoMoreInteractions(binder);
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 2ed11fe..fa472e2 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -18,11 +18,14 @@
import android.annotation.UserIdInt;
import android.app.PendingIntent;
+import android.app.usage.UsageStatsManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -163,6 +166,9 @@
/** Map of observerId to details of the time limit group */
SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
+ /** Map of observerId to details of the app usage limit group */
+ SparseArray<AppUsageLimitGroup> appUsageLimitGroups = new SparseArray<>();
+
private ObserverAppData(int uid) {
this.uid = uid;
}
@@ -177,6 +183,10 @@
sessionUsageGroups.remove(observerId);
}
+ @GuardedBy("mLock")
+ void removeAppUsageLimitGroup(int observerId) {
+ appUsageLimitGroups.remove(observerId);
+ }
@GuardedBy("mLock")
void dump(PrintWriter pw) {
@@ -194,6 +204,12 @@
sessionUsageGroups.valueAt(i).dump(pw);
pw.println();
}
+ pw.println(" App Usage Limit Groups:");
+ final int nAppUsageLimitGroups = appUsageLimitGroups.size();
+ for (int i = 0; i < nAppUsageLimitGroups; i++) {
+ appUsageLimitGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
}
}
@@ -493,6 +509,54 @@
}
}
+ class AppUsageLimitGroup extends UsageGroup {
+ private boolean mGroupLimit;
+
+ public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ mGroupLimit = observed.length > 1;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeAppUsageLimitGroup(mObserverId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ boolean isGroupLimit() {
+ return mGroupLimit;
+ }
+
+ @GuardedBy("mLock")
+ long getTotaUsageLimit() {
+ return mTimeLimitMs;
+ }
+
+ @GuardedBy("mLock")
+ long getUsageRemaining() {
+ // If there is currently an active session, account for its usage
+ if (mActives > 0) {
+ return mTimeLimitMs - mUsageTimeMs - (getUptimeMillis() - mLastKnownUsageTimeMs);
+ } else {
+ return mTimeLimitMs - mUsageTimeMs;
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.print(" groupLimit=");
+ pw.print(mGroupLimit);
+ }
+ }
+
private class MyHandler extends Handler {
static final int MSG_CHECK_TIMEOUT = 1;
@@ -553,6 +617,12 @@
/** Overrideable for testing purposes */
@VisibleForTesting
+ protected long getAppUsageLimitObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ /** Overrideable for testing purposes */
+ @VisibleForTesting
protected long getMinTimeLimit() {
return ONE_MINUTE;
}
@@ -572,6 +642,61 @@
}
}
+ @VisibleForTesting
+ AppUsageLimitGroup getAppUsageLimitGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).appUsageLimitGroups.get(
+ observerId);
+ }
+ }
+
+ /**
+ * Returns an object describing the app usage limit for the given package which was set via
+ * {@link #addAppUsageLimitObserver).
+ * If there are multiple limits that apply to the package, the one with the smallest
+ * time remaining will be returned.
+ */
+ public UsageStatsManagerInternal.AppUsageLimitData getAppUsageLimit(
+ String packageName, UserHandle user) {
+ synchronized (mLock) {
+ final UserData userData = getOrCreateUserDataLocked(user.getIdentifier());
+ if (userData == null) {
+ return null;
+ }
+
+ final ArrayList<UsageGroup> usageGroups = userData.observedMap.get(packageName);
+ if (usageGroups == null || usageGroups.isEmpty()) {
+ return null;
+ }
+
+ final ArraySet<AppUsageLimitGroup> usageLimitGroups = new ArraySet<>();
+ for (int i = 0; i < usageGroups.size(); i++) {
+ if (usageGroups.get(i) instanceof AppUsageLimitGroup) {
+ final AppUsageLimitGroup group = (AppUsageLimitGroup) usageGroups.get(i);
+ for (int j = 0; j < group.mObserved.length; j++) {
+ if (group.mObserved[j].equals(packageName)) {
+ usageLimitGroups.add(group);
+ break;
+ }
+ }
+ }
+ }
+ if (usageLimitGroups.isEmpty()) {
+ return null;
+ }
+
+ AppUsageLimitGroup smallestGroup = usageLimitGroups.valueAt(0);
+ for (int i = 1; i < usageLimitGroups.size(); i++) {
+ final AppUsageLimitGroup otherGroup = usageLimitGroups.valueAt(i);
+ if (otherGroup.getUsageRemaining() < smallestGroup.getUsageRemaining()) {
+ smallestGroup = otherGroup;
+ }
+ }
+ return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+ smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
+ }
+ }
+
/** Returns an existing UserData object for the given userId, or creates one */
@GuardedBy("mLock")
private UserData getOrCreateUserDataLocked(int userId) {
@@ -726,6 +851,61 @@
}
/**
+ * Registers an app usage limit observer with the given details.
+ * Existing app usage limit observer with the same observerId will be removed.
+ */
+ public void addAppUsageLimitObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
+ if (timeLimit < getMinTimeLimit()) {
+ throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+ }
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+
+ final int observerIdCount = observerApp.appUsageLimitGroups.size();
+ if (observerIdCount >= getAppUsageLimitObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
+ }
+ group = new AppUsageLimitGroup(user, observerApp, observerId, observed, timeLimit,
+ callbackIntent);
+ observerApp.appUsageLimitGroups.append(observerId, group);
+
+ if (DEBUG) {
+ Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
+ }
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
+ }
+ }
+
+ /**
+ * Remove a registered observer by observerId and calling uid.
+ *
+ * @param requestingUid The calling uid
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
+ */
+ public void removeAppUsageLimitObserver(int requestingUid, int observerId,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ final ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ final AppUsageLimitGroup group = observerApp.appUsageLimitGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ group.remove();
+ }
+ }
+ }
+
+ /**
* Called when an entity becomes active.
*
* @param name The entity that became active
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6ad698b..85939d4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -855,6 +855,22 @@
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean hasPermissions(String callingPackage, String... permissions) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.SYSTEM_UID) {
+ // Caller is the system, so proceed.
+ return true;
+ }
+
+ boolean hasPermissions = true;
+ final Context context = getContext();
+ for (int i = 0; i < permissions.length; i++) {
+ hasPermissions = hasPermissions && (context.checkCallingPermission(permissions[i])
+ == PackageManager.PERMISSION_GRANTED);
+ }
+ return hasPermissions;
+ }
+
private void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallingUidSystem()) {
return;
@@ -1346,6 +1362,51 @@
}
@Override
+ public void registerAppUsageLimitObserver(int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ if (packages == null || packages.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one package");
+ }
+ if (callbackIntent == null) {
+ throw new NullPointerException("callbackIntent can't be null");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.registerAppUsageLimitObserver(callingUid, observerId,
+ packages, timeLimitMs, callbackIntent, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) {
+ if (!hasPermissions(callingPackage,
+ Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE)) {
+ throw new SecurityException("Caller doesn't have both SUSPEND_APPS and "
+ + "OBSERVE_APP_USAGE permissions");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.unregisterAppUsageLimitObserver(
+ callingUid, observerId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void reportUsageStart(IBinder activity, String token, String callingPackage) {
reportPastUsageStart(activity, token, 0, callingPackage);
}
@@ -1447,6 +1508,16 @@
mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
+ void registerAppUsageLimitObserver(int callingUid, int observerId, String[] packages,
+ long timeLimitMs, PendingIntent callbackIntent, int userId) {
+ mAppTimeLimit.addAppUsageLimitObserver(callingUid, observerId, packages, timeLimitMs,
+ callbackIntent, userId);
+ }
+
+ void unregisterAppUsageLimitObserver(int callingUid, int observerId, int userId) {
+ mAppTimeLimit.removeAppUsageLimitObserver(callingUid, observerId, userId);
+ }
+
/**
* This local service implementation is primarily used by ActivityManagerService.
* ActivityManagerService will call these methods holding the 'am' lock, which means we
@@ -1652,5 +1723,10 @@
public void reportExemptedSyncStart(String packageName, int userId) {
mAppStandby.postReportExemptedSyncStart(packageName, userId);
}
+
+ @Override
+ public AppUsageLimitData getAppUsageLimit(String packageName, UserHandle user) {
+ return mAppTimeLimit.getAppUsageLimit(packageName, user);
+ }
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 613c4ff..bb01f04 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -19,9 +19,6 @@
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-
-import com.android.internal.app.IVoiceActionCheckCallback;
-import com.android.server.wm.ActivityTaskManagerInternal;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -62,8 +59,9 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.app.IVoiceInteractionSessionListener;
+import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
@@ -75,6 +73,7 @@
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.soundtrigger.SoundTriggerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1205,6 +1204,57 @@
mSoundTriggerInternal.dump(fd, pw, args);
}
+ @Override
+ public void setTranscription(String transcription) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onTranscriptionUpdate(transcription);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice transcription.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ @Override
+ public void clearTranscription(boolean immediate) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onTranscriptionComplete(immediate);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering transcription complete event.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ @Override
+ public void setVoiceState(int state) {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onVoiceStateChange(state);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice state change.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
private void enforceCallingPermission(String permission) {
if (mContext.checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 15abdb7..5482270 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2375,6 +2375,9 @@
/**
* Contains message parts.
+ *
+ * To avoid issues where applications might cache a part ID, the ID of a deleted part must
+ * not be reused to point at a new part.
*/
public static final class Part implements BaseColumns {
@@ -2386,6 +2389,12 @@
}
/**
+ * The {@code content://} style URL for this table. Can be appended with a part ID to
+ * address individual parts.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+
+ /**
* The identifier of the message which this part belongs to.
* <P>Type: INTEGER</P>
*/
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 1378bb0..d777bf1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -22,6 +22,10 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.bluetooth.BluetoothProfile;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Context;
@@ -32,6 +36,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telecom.PhoneAccount;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -61,6 +66,8 @@
*/
public final class SmsManager {
private static final String TAG = "SmsManager";
+ private static final boolean DBG = false;
+
/**
* A psuedo-subId that represents the default subId at any given time. The actual subId it
* represents changes as the default subId is changed.
@@ -339,6 +346,44 @@
throw new IllegalArgumentException("Invalid message body");
}
+ // A Manager code accessing another manager is *not* acceptable, in Android.
+ // In this particular case, it is unavoidable because of the following:
+ // If the subscription for this SmsManager instance belongs to a remote SIM
+ // then a listener to get BluetoothMapClient proxy needs to be started up.
+ // Doing that is possible only in a foreground thread or as a system user.
+ // i.e., Can't be done in ISms service.
+ // For that reason, SubscriptionManager needs to be accessed here to determine
+ // if the subscription belongs to a remote SIM.
+ // Ideally, there should be another API in ISms to service messages going thru
+ // remote SIM subscriptions (and ISms should be tweaked to be able to access
+ // BluetoothMapClient proxy)
+ Context context = ActivityThread.currentApplication().getApplicationContext();
+ SubscriptionManager manager = (SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ int subId = getSubscriptionId();
+ SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId);
+ if (DBG) {
+ Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info);
+ }
+ if (info == null) {
+ // There is no subscription for the given subId. That can only mean one thing:
+ // the caller is using a SmsManager instance with an obsolete subscription id.
+ // That is most probably because caller didn't invalidate SmsManager instance
+ // for an already deleted subscription id.
+ Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete.");
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ }
+
+ /* If the Subscription associated with this SmsManager instance belongs to a remote-sim,
+ * then send the message thru the remote-sim subscription.
+ */
+ if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
+ if (DBG) Log.d(TAG, "sending message thru bluetooth");
+ sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent,
+ deliveryIntent, info);
+ return;
+ }
+
try {
ISms iccISms = getISmsServiceOrThrow();
iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
@@ -350,6 +395,79 @@
}
}
+ private void sendTextMessageBluetooth(String destAddr, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ SubscriptionInfo info) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ // No bluetooth service on this platform?
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
+ if (device == null) {
+ if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId());
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
+ new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
+ BluetoothProfile.MAP_CLIENT);
+ }
+
+ private class MapMessageSender implements BluetoothProfile.ServiceListener {
+ final Uri[] mDestAddr;
+ private String mMessage;
+ final BluetoothDevice mDevice;
+ final PendingIntent mSentIntent;
+ final PendingIntent mDeliveryIntent;
+ MapMessageSender(final String destAddr, final String message, final BluetoothDevice device,
+ final PendingIntent sentIntent, final PendingIntent deliveryIntent) {
+ super();
+ mDestAddr = new Uri[] {new Uri.Builder()
+ .appendPath(destAddr)
+ .scheme(PhoneAccount.SCHEME_TEL)
+ .build()};
+ mMessage = message;
+ mDevice = device;
+ mSentIntent = sentIntent;
+ mDeliveryIntent = deliveryIntent;
+ }
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DBG) Log.d(TAG, "Service connected");
+ if (profile != BluetoothProfile.MAP_CLIENT) return;
+ BluetoothMapClient mapProfile = (BluetoothMapClient) proxy;
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Sending message thru bluetooth");
+ mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent);
+ mMessage = null;
+ }
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message");
+ sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ mMessage = null;
+ }
+ }
+ }
+
+ private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) {
+ try {
+ intent.send(errorCode);
+ } catch (PendingIntent.CanceledException e) {
+ // PendingIntent is cancelled. ignore sending this error code back to
+ // caller.
+ if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage());
+ }
+ }
+
/**
* Send a text based SMS without writing it into the SMS Provider.
*
@@ -888,8 +1006,6 @@
}
}
-
-
/**
* Get the SmsManager associated with the default subscription id. The instance will always be
* associated with the default subscription id, even if the default subscription id is changed.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 51d5ab1..a3b3374 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -184,6 +184,11 @@
private int mProfileClass;
/**
+ * Type of subscription
+ */
+ private int mSubscriptionType;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -206,7 +211,8 @@
@Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
- isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
+ isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass,
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
}
/**
@@ -217,7 +223,7 @@
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
- boolean isGroupDisabled, int carrierid, int profileClass) {
+ boolean isGroupDisabled, int carrierId, int profileClass, int subType) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -239,11 +245,11 @@
this.mGroupUUID = groupUUID;
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
- this.mCarrierId = carrierid;
+ this.mCarrierId = carrierId;
this.mProfileClass = profileClass;
+ this.mSubscriptionType = subType;
}
-
/**
* @return the subscription ID.
*/
@@ -487,6 +493,16 @@
}
/**
+ * This method returns the type of a subscription. It can be
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @return the type of subscription
+ */
+ public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
+ return mSubscriptionType;
+ }
+
+ /**
* Checks whether the app with the given context is authorized to manage this subscription
* according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
* returns true).
@@ -612,11 +628,12 @@
boolean isGroupDisabled = source.readBoolean();
int carrierid = source.readInt();
int profileClass = source.readInt();
+ int subType = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
- isMetered, isGroupDisabled, carrierid, profileClass);
+ isMetered, isGroupDisabled, carrierid, profileClass, subType);
}
@Override
@@ -650,6 +667,7 @@
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
dest.writeInt(mProfileClass);
+ dest.writeInt(mSubscriptionType);
}
@Override
@@ -686,7 +704,8 @@
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
+ " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
- + " profileClass=" + mProfileClass + "}";
+ + " profileClass=" + mProfileClass
+ + " subscriptionType=" + mSubscriptionType + "}";
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9fa4c3c..869cf1c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -247,7 +247,9 @@
public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
/**
- * TelephonyProvider column name for SIM ICC Identifier
+ * TelephonyProvider column name for a unique identifier for the subscription within the
+ * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+ * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
@@ -265,6 +267,63 @@
public static final int SIM_NOT_INSERTED = -1;
/**
+ * The slot-index for Bluetooth Remote-SIM subscriptions
+ * @hide
+ */
+ public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX;
+
+ /**
+ * TelephonyProvider column name Subscription-type.
+ * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions,
+ * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+ * Default value is 0.
+ */
+ /** @hide */
+ public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+ /**
+ * This constant is to designate a subscription as a Local-SIM Subscription.
+ * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
+ * device.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+ /**
+ * This constant is to designate a subscription as a Remote-SIM Subscription.
+ * <p>
+ * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+ * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can
+ * be used for SMS, Voice and data by proxying data through the connected device.
+ * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+ * </p>
+ *
+ * <p>
+ * A Remote-SIM is available only as long the phone stays connected to this device.
+ * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+ * no longer known. All data associated with the subscription, such as stored SMS, call logs,
+ * contacts etc, are removed from this device.
+ * </p>
+ *
+ * <p>
+ * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+ * the phone. The Subscription Id associated with the new subscription is different from
+ * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+ * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that
+ * was never seen before.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+ value = {
+ SUBSCRIPTION_TYPE_LOCAL_SIM,
+ SUBSCRIPTION_TYPE_REMOTE_SIM})
+ public @interface SubscriptionType {}
+
+ /**
* TelephonyProvider column name for user displayed name.
* <P>Type: TEXT (String)</P>
*/
@@ -1145,7 +1204,7 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
+ * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
* by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1427,17 +1486,7 @@
logd("[addSubscriptionInfoRecord]- invalid slotIndex");
}
- try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
- if (iSub != null) {
- // FIXME: This returns 1 on success, 0 on error should should we return it?
- iSub.addSubInfoRecord(iccId, slotIndex);
- } else {
- logd("[addSubscriptionInfoRecord]- ISub service is null");
- }
- } catch (RemoteException ex) {
- // ignore it
- }
+ addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM);
// FIXME: Always returns null?
return null;
@@ -1445,6 +1494,79 @@
}
/**
+ * Add a new SubscriptionInfo to SubscriptionInfo database if needed
+ * @param uniqueId This is the unique identifier for the subscription within the
+ * specific subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType
+ * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex,
+ int subscriptionType) {
+ if (VDBG) {
+ logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", displayName:" + displayName + ", slotIndex:" + slotIndex
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully added new subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Remove SubscriptionInfo record from the SubscriptionInfo database
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) {
+ if (VDBG) {
+ logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.removeSubInfo(uniqueId, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully removed subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the RGB value of icon tint color of the SIM
* @param subId the unique SubInfoRecord index in database
@@ -2737,6 +2859,95 @@
}
}
+ /**
+ * Enabled or disable a subscription. This is currently used in the settings page.
+ *
+ * <p>
+ * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+ *
+ * @param enable whether user is turning it on or off.
+ * @param subscriptionId Subscription to be enabled or disabled.
+ * It could be a eSIM or pSIM subscription.
+ *
+ * @return whether the operation is successful.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public boolean setSubscriptionEnabled(int subscriptionId, boolean enable) {
+ if (VDBG) {
+ logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
+ }
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ return iSub.setSubscriptionEnabled(enable, subscriptionId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the subscription is enabled or not. This is different from activated
+ * or deactivated for two aspects. 1) For when user disables a physical subscription, we
+ * actually disable the modem because we can't switch off the subscription. 2) For eSIM,
+ * user may enable one subscription but the system may activate another temporarily. In this
+ * case, user enabled one is different from current active one.
+
+ * @param subscriptionId The subscription it asks about.
+ * @return whether it's enabled or not. {@code true} if user set this subscription enabled
+ * earlier, or user never set subscription enable / disable on this slot explicitly, and
+ * this subscription is currently active. Otherwise, it returns {@code false}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isSubscriptionEnabled(int subscriptionId) {
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ return iSub.isSubscriptionEnabled(subscriptionId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return false;
+ }
+
+ /**
+ * Get which subscription is enabled on this slot. See {@link #isSubscriptionEnabled(int)}
+ * for more details.
+ *
+ * @param slotIndex which slot it asks about.
+ * @return which subscription is enabled on this slot. If there's no enabled subscription
+ * in this slot, it will return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getEnabledSubscriptionId(int slotIndex) {
+ int subId = INVALID_SUBSCRIPTION_ID;
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ subId = iSub.getEnabledSubscriptionId(slotIndex);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ if (VDBG) logd("getEnabledSubscriptionId, subId = " + subId);
+ return subId;
+ }
+
private interface CallISubMethodHelper {
int callMethod(ISub iSub) throws RemoteException;
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 577ddbd..a49d2d9 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,8 +52,8 @@
/**
* Get the active SubscriptionInfo associated with the slotIndex
* @param slotIndex the slot which the subscription is inserted
- * @param callingPackage The package maing the call.
- * @return SubscriptionInfo, maybe null if its not active
+ * @param callingPackage The package making the call.
+ * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
*/
SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
@@ -115,6 +115,26 @@
int addSubInfoRecord(String iccId, int slotIndex);
/**
+ * Add a new subscription info record, if needed
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this device
+ * @param subscriptionType the type of subscription to be added.
+ * @return 0 if success, < 0 on error.
+ */
+ int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType);
+
+ /**
+ * Remove subscription info record for the given device.
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the type of subscription to be removed
+ * @return 0 if success, < 0 on error.
+ */
+ int removeSubInfo(String uniqueId, int subscriptionType);
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the icon tint color of the SIM
* @param subId the unique SubscriptionInfo index in database
@@ -256,6 +276,11 @@
String getSubscriptionProperty(int subId, String propKey, String callingPackage);
+ boolean setSubscriptionEnabled(boolean enable, int subId);
+
+ boolean isSubscriptionEnabled(int subId);
+
+ int getEnabledSubscriptionId(int slotIndex);
/**
* Get the SIM state for the slot index
* @return SIM state as the ordinal of IccCardConstants.State
diff --git a/tests/UsageStatsTest/AndroidManifest.xml b/tests/UsageStatsTest/AndroidManifest.xml
index 4b1c1bd..fefd993 100644
--- a/tests/UsageStatsTest/AndroidManifest.xml
+++ b/tests/UsageStatsTest/AndroidManifest.xml
@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
+ <uses-permission android:name="android.permission.SUSPEND_APPS" />
<application android:label="Usage Access Test">
<activity android:name=".UsageStatsActivity"
diff --git a/tests/UsageStatsTest/res/menu/main.xml b/tests/UsageStatsTest/res/menu/main.xml
index 612267c..272e0f4 100644
--- a/tests/UsageStatsTest/res/menu/main.xml
+++ b/tests/UsageStatsTest/res/menu/main.xml
@@ -6,4 +6,6 @@
android:title="Call isAppInactive()"/>
<item android:id="@+id/set_app_limit"
android:title="Set App Limit" />
+ <item android:id="@+id/set_app_usage_limit"
+ android:title="Set App Usage Limit" />
</menu>
diff --git a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
index 3c628f6..0105893 100644
--- a/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
+++ b/tests/UsageStatsTest/src/com/android/tests/usagestats/UsageStatsActivity.java
@@ -21,6 +21,8 @@
import android.app.PendingIntent;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -49,6 +51,8 @@
private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
private static final String EXTRA_KEY_TIMEOUT = "com.android.tests.usagestats.extra.TIMEOUT";
private UsageStatsManager mUsageStatsManager;
+ private ClipboardManager mClipboard;
+ private ClipData mClip;
private Adapter mAdapter;
private Comparator<UsageStats> mComparator = new Comparator<UsageStats>() {
@Override
@@ -61,6 +65,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
+ mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
mAdapter = new Adapter();
setListAdapter(mAdapter);
Bundle extras = getIntent().getExtras();
@@ -98,6 +103,8 @@
case R.id.set_app_limit:
callSetAppLimit();
return true;
+ case R.id.set_app_usage_limit:
+ callSetAppUsageLimit();
default:
return super.onOptionsItemSelected(item);
}
@@ -170,6 +177,40 @@
builder.show();
}
+ private void callSetAppUsageLimit() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Enter package name");
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setHint("com.android.tests.usagestats");
+ builder.setView(input);
+
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final String packageName = input.getText().toString().trim();
+ if (!TextUtils.isEmpty(packageName)) {
+ String[] packages = packageName.split(",");
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(UsageStatsActivity.this, UsageStatsActivity.class);
+ intent.setPackage(getPackageName());
+ intent.putExtra(EXTRA_KEY_TIMEOUT, true);
+ mUsageStatsManager.registerAppUsageLimitObserver(1, packages,
+ 60, TimeUnit.SECONDS, PendingIntent.getActivity(UsageStatsActivity.this,
+ 1, intent, 0));
+ }
+ }
+ });
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
private void showInactive(String packageName) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(
@@ -232,6 +273,21 @@
holder.packageName.setText(mStats.get(position).getPackageName());
holder.usageTime.setText(DateUtils.formatDuration(
mStats.get(position).getTotalTimeInForeground()));
+
+ //copy package name to the clipboard for convenience
+ holder.packageName.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ String text = holder.packageName.getText().toString();
+ mClip = ClipData.newPlainText("package_name", text);
+ mClipboard.setPrimaryClip(mClip);
+
+ Toast.makeText(getApplicationContext(), "package name copied to clipboard",
+ Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ });
+
return convertView;
}
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dda4481..923c7dd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -107,6 +107,8 @@
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
@@ -121,7 +123,9 @@
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkUtils;
+import android.net.ProxyInfo;
import android.net.RouteInfo;
+import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -158,6 +162,7 @@
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
@@ -186,6 +191,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -402,8 +409,8 @@
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
- private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
- private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+ private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+ private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
// mNetworkStatusReceived.
@@ -1002,6 +1009,11 @@
}
@Override
+ protected ProxyTracker makeProxyTracker() {
+ return mock(ProxyTracker.class);
+ }
+
+ @Override
protected int reserveNetId() {
while (true) {
final int netId = super.reserveNetId();
@@ -1023,6 +1035,11 @@
}
}
+ @Override
+ protected boolean queryUserAccess(int uid, int netId) {
+ return true;
+ }
+
public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@@ -3548,6 +3565,80 @@
}
}
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ private class CallbackValue {
+ public CallbackType callbackType;
+ public int error;
+
+ CallbackValue(CallbackType type) {
+ this.callbackType = type;
+ this.error = SocketKeepalive.SUCCESS;
+ assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+ }
+
+ CallbackValue(CallbackType type, int error) {
+ this.callbackType = type;
+ this.error = error;
+ assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof CallbackValue
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+ error);
+ }
+ }
+
+ private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ @Override
+ public void onError(int error) {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+
+ private void expectCallback(CallbackValue callbackValue) {
+ try {
+ assertEquals(
+ callbackValue,
+ mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+ }
+ }
+
+ public void expectStarted() {
+ expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ public void expectStopped() {
+ expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ public void expectError(int error) {
+ expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+ }
+
private Network connectKeepaliveNetwork(LinkProperties lp) {
// Ensure the network is disconnected before we do anything.
if (mWiFiNetworkAgent != null) {
@@ -3695,6 +3786,145 @@
}
@Test
+ public void testNattSocketKeepalives() throws Exception {
+ // TODO: 1. Move this outside of ConnectivityServiceTest.
+ // 2. Add helper function to test against newSingleThreadExecutor as well as inline
+ // executor.
+ // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+ final int srcPort = 12345;
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+ final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+ final int validKaInterval = 15;
+ final int invalidKaInterval = 9;
+
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+
+ final Executor executor = Executors.newSingleThreadExecutor();
+
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+ Network notMyNet = new Network(61234);
+ Network myNet = connectKeepaliveNetwork(lp);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka;
+
+ // Attempt to start keepalives with invalid parameters and check for errors.
+ // Invalid network.
+ ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // Invalid interval.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(invalidKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+
+ // Invalid destination.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Invalid source;
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // NAT-T is only supported for IPv4.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Sanity check before testing started keepalive.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+ // Check that a started keepalive can be stopped.
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that deleting the IP address stops the keepalive.
+ LinkProperties bogusLp = new LinkProperties(lp);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+ bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+ mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ mWiFiNetworkAgent.sendLinkProperties(lp);
+
+ // Check that a started keepalive is stopped correctly when the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // ... and that stopping it after that has no adverse effects.
+ waitForIdle();
+ final Network myNetAlias = myNet;
+ assertNull(mCm.getNetworkCapabilities(myNetAlias));
+ ka.stop();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check things work as expected when the keepalive is stopped and the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ waitForIdle();
+ callback.expectStopped();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+
+ // The second one gets slot 2.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+ TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka2 =
+ mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
+ ka2.start(validKaInterval);
+ callback2.expectStarted();
+
+ ka.stop();
+ callback.expectStopped();
+
+ ka2.stop();
+ callback2.expectStopped();
+ }
+
+ @Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();
assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
@@ -4914,4 +5144,84 @@
mCellNetworkAgent.sendLinkProperties(lp);
verifyTcpBufferSizeChange(TEST_TCP_BUFFER_SIZES);
}
+
+ @Test
+ public void testGetGlobalProxyForNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+ when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
+ }
+
+ @Test
+ public void testGetProxyForActiveNetwork() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
+
+ @Test
+ public void testGetProxyForVPN() {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+
+ // Set up a WiFi network with no proxy
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set up a VPN network with a proxy
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setUids(ranges);
+ LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ // Connect to VPN with proxy
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+
+ // Test that the VPN network returns a proxy, and the WiFi does not.
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+
+ // Test that the VPN network returns no proxy when it is set to null.
+ testLinkProperties.setHttpProxy(null);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set WiFi proxy and check that the vpn proxy is still null.
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Disconnect from VPN and check that the active network, which is now the WiFi, has the
+ // correct proxy setting.
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe725..273b8fc 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -34,26 +35,24 @@
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkNotificationManagerTest {
@@ -194,4 +193,54 @@
mManager.clearNotification(id);
verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
}
+
+ @Test
+ public void testSameLevelNotifications() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+ }
+
+ @Test
+ public void testClearNotificationByType() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+ // to previous type or not. If they are equal then clear the notification; if they are not
+ // equal then return.
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+ // should be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ verify(mNotificationManager, times(1))
+ .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(2))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // LOST_INTERNET notification popup after LOGGED_IN notification.
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+ // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+ // shouldn't be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ // LOST_INTERNET shouldn't be cleared.
+ verify(mNotificationManager, never())
+ .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+ }
}
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 2e43150..8da5bb8 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -53,7 +53,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -87,7 +91,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_convert_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
int32_t raw_index = tree.getAttributeValueStringID(0);
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3c8b72d..9ea93f6 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -43,7 +43,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has not been assigned
EXPECT_THAT(tree.getAttributeValueStringID(0), Eq(-1));
@@ -67,7 +71,11 @@
// Load the binary xml tree
android::ResXMLTree tree;
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag);
- AssertLoadXml(apk.get(), "res/xml/test.xml", &tree);
+
+ std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml");
+ ASSERT_THAT(data, Ne(nullptr));
+
+ AssertLoadXml(apk.get(), data.get(), &tree);
// Check that the raw string index has been set to the correct string pool entry
int32_t raw_index = tree.getAttributeValueStringID(0);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index aae79fa..3fcdfb7 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -133,16 +133,18 @@
return manifest_file;
}
-void CommandTestFixture::AssertLoadXml(LoadedApk *apk, const android::StringPiece &xml_path,
+std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
+ const android::StringPiece& path) {
+ return apk
+ ->GetFileCollection()
+ ->FindFile(path)
+ ->OpenAsData();
+}
+
+void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data,
android::ResXMLTree *out_tree) {
ASSERT_THAT(apk, Ne(nullptr));
- io::IFile* file = apk->GetFileCollection()->FindFile(xml_path);
- ASSERT_THAT(file, Ne(nullptr));
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- ASSERT_THAT(data, Ne(nullptr));
-
out_tree->setTo(data->data(), data->size());
ASSERT_THAT(out_tree->getError(), Eq(android::OK));
while (out_tree->next() != android::ResXMLTree::START_TAG) {
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 89d3b7b..3079c75 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -83,8 +83,12 @@
// Creates a minimal android manifest within the test directory and returns the file path.
std::string GetDefaultManifest();
+ // Returns pointer to data inside APK files
+ std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
+ const android::StringPiece& path);
+
// Asserts that loading the tree from the specified file in the apk succeeds.
- void AssertLoadXml(LoadedApk* apk, const android::StringPiece& xml_path,
+ void AssertLoadXml(LoadedApk* apk, const io::IData* data,
android::ResXMLTree* out_tree);
private:
diff --git a/tools/signedconfig/debug_key.pem b/tools/signedconfig/debug_key.pem
index 0af577b..17a1dff 100644
--- a/tools/signedconfig/debug_key.pem
+++ b/tools/signedconfig/debug_key.pem
@@ -1,5 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
-MHcCAQEEIEfgtO+KPOoqJqTnqkDDKkAcOzyvtovsUO/ShLE6y4XRoAoGCCqGSM49
-AwEHoUQDQgAEaAn2XVifsLTHg616nTsOMVmlhBoECGbTEBTKKvdd2hO60pj1pnU8
-SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MHcCAQEEIFbNNr1/TsFlvnmH1z6e0xyact9t7PDs+VFWc7QFtoRcoAoGCCqGSM49
+AwEHoUQDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13YrU1haIhVC5296InRu1x7A8PV1
+ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END EC PRIVATE KEY-----
diff --git a/tools/signedconfig/debug_public.pem b/tools/signedconfig/debug_public.pem
index f61f813..d9f0d38 100644
--- a/tools/signedconfig/debug_public.pem
+++ b/tools/signedconfig/debug_public.pem
@@ -1,4 +1,4 @@
-----BEGIN PUBLIC KEY-----
-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaAn2XVifsLTHg616nTsOMVmlhBoE
-CGbTEBTKKvdd2hO60pj1pnU8SMkhYfaNxZuKgw9LNvOwlFwStboIYeZ3lQ==
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmJKs4lSn+XRhMQmMid+Zbhbu13Yr
+U1haIhVC5296InRu1x7A8PV1ejQyisBODGgRY6pqkAHRncBCYcgg5wIIJg==
-----END PUBLIC KEY-----
diff --git a/tools/signedconfig/debug_sign.sh b/tools/signedconfig/debug_sign.sh
index 28e5428..3a2814a 100755
--- a/tools/signedconfig/debug_sign.sh
+++ b/tools/signedconfig/debug_sign.sh
@@ -2,5 +2,5 @@
# Script to sign data with the debug keys. Outputs base64 for embedding into
# APK metadata.
-openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem $1 | base64 -w 0
+openssl dgst -sha256 -sign $(dirname $0)/debug_key.pem <(echo -n "$1" | base64 -d) | base64 -w 0
echo
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 46c4191..a0ce9dd 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -28,6 +28,7 @@
import android.net.wifi.INetworkRequestMatchCallback;
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.IWifiUsabilityStatsListener;
import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -186,6 +187,10 @@
void unregisterSoftApCallback(int callbackIdentifier);
+ void addWifiUsabilityStatsListener(in IBinder binder, in IWifiUsabilityStatsListener listener, int listenerIdentifier);
+
+ void removeWifiUsabilityStatsListener(int listenerIdentifier);
+
void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier);
void unregisterTrafficStateCallback(int callbackIdentifier);
@@ -210,4 +215,3 @@
void stopDppSession();
}
-
diff --git a/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
new file mode 100644
index 0000000..284ffaa
--- /dev/null
+++ b/wifi/java/android/net/wifi/IWifiUsabilityStatsListener.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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.net.wifi;
+
+import android.net.wifi.WifiUsabilityStatsEntry;
+
+/**
+ * Interface for Wi-Fi usability stats listener.
+ *
+ * @hide
+ */
+oneway interface IWifiUsabilityStatsListener
+{
+ /**
+ * Service to manager callback providing current Wi-Fi usability stats.
+ *
+ * @param seqNum The sequence number of stats, used to derive the timing of updated Wi-Fi
+ * usability statistics, set by framework and shall be incremented by one
+ * after each update.
+ * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+ * network stays the same or not relative to the last update of
+ * Wi-Fi usability stats.
+ * @param stats The updated Wi-Fi usability statistics.
+ */
+ void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ in WifiUsabilityStatsEntry stats);
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1fd45e7..084bd09 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -4777,4 +4777,92 @@
});
}
}
+
+ /**
+ * Interface for Wi-Fi usability statistics listener. Should be implemented by applications and
+ * set when calling {@link WifiManager#addWifiUsabilityStatsListener(Executor,
+ * WifiUsabilityStatsListener)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public interface WifiUsabilityStatsListener {
+ /**
+ * Called when Wi-Fi usability statistics is updated.
+ *
+ * @param seqNum The sequence number of statistics, used to derive the timing of updated
+ * Wi-Fi usability statistics, set by framework and incremented by one after
+ * each update.
+ * @param isSameBssidAndFreq The flag to indicate whether the BSSID and the frequency of
+ * network stays the same or not relative to the last update of
+ * Wi-Fi usability stats.
+ * @param stats The updated Wi-Fi usability statistics.
+ */
+ void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ WifiUsabilityStatsEntry stats);
+ }
+
+ /**
+ * Adds a listener for Wi-Fi usability statistics. See {@link WifiUsabilityStatsListener}.
+ * Multiple listeners can be added. Callers will be invoked periodically by framework to
+ * inform clients about the current Wi-Fi usability statistics. Callers can remove a previously
+ * added listener using {@link removeWifiUsabilityStatsListener}.
+ *
+ * @param executor The executor on which callback will be invoked.
+ * @param listener Listener for Wifi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void addWifiUsabilityStatsListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull WifiUsabilityStatsListener listener) {
+ if (executor == null) throw new IllegalArgumentException("executor cannot be null");
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "addWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.addWifiUsabilityStatsListener(new Binder(),
+ new IWifiUsabilityStatsListener.Stub() {
+ @Override
+ public void onStatsUpdated(int seqNum, boolean isSameBssidAndFreq,
+ WifiUsabilityStatsEntry stats) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "WifiUsabilityStatsListener: onStatsUpdated: seqNum="
+ + seqNum);
+ }
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> listener.onStatsUpdated(seqNum,
+ isSameBssidAndFreq, stats)));
+ }
+ },
+ listener.hashCode()
+ );
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to remove a previously registered listener. After calling this method,
+ * applications will no longer receive Wi-Fi usability statistics.
+ *
+ * @param listener Listener to remove the Wi-Fi usability statistics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE)
+ public void removeWifiUsabilityStatsListener(@NonNull WifiUsabilityStatsListener listener) {
+ if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "removeWifiUsabilityStatsListener: listener=" + listener);
+ }
+ try {
+ mService.removeWifiUsabilityStatsListener(listener.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
new file mode 100644
index 0000000..839af54
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.net.wifi;
+
+parcelable WifiUsabilityStatsEntry;
diff --git a/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
new file mode 100644
index 0000000..c796e29
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiUsabilityStatsEntry.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 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.net.wifi;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class makes a subset of
+ * com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStatsEntry parcelable.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WifiUsabilityStatsEntry implements Parcelable {
+ /** Absolute milliseconds from device boot when these stats were sampled */
+ public final long timeStampMs;
+ /** The RSSI (in dBm) at the sample time */
+ public final int rssi;
+ /** Link speed at the sample time in Mbps */
+ public final int linkSpeedMbps;
+ /** The total number of tx success counted from the last radio chip reset */
+ public final long totalTxSuccess;
+ /** The total number of MPDU data packet retries counted from the last radio chip reset */
+ public final long totalTxRetries;
+ /** The total number of tx bad counted from the last radio chip reset */
+ public final long totalTxBad;
+ /** The total number of rx success counted from the last radio chip reset */
+ public final long totalRxSuccess;
+ /** The total time the wifi radio is on in ms counted from the last radio chip reset */
+ public final long totalRadioOnTimeMs;
+ /** The total time the wifi radio is doing tx in ms counted from the last radio chip reset */
+ public final long totalRadioTxTimeMs;
+ /** The total time the wifi radio is doing rx in ms counted from the last radio chip reset */
+ public final long totalRadioRxTimeMs;
+ /** The total time spent on all types of scans in ms counted from the last radio chip reset */
+ public final long totalScanTimeMs;
+ /** The total time spent on nan scans in ms counted from the last radio chip reset */
+ public final long totalNanScanTimeMs;
+ /** The total time spent on background scans in ms counted from the last radio chip reset */
+ public final long totalBackgroundScanTimeMs;
+ /** The total time spent on roam scans in ms counted from the last radio chip reset */
+ public final long totalRoamScanTimeMs;
+ /** The total time spent on pno scans in ms counted from the last radio chip reset */
+ public final long totalPnoScanTimeMs;
+ /** The total time spent on hotspot2.0 scans and GAS exchange in ms counted from the last radio
+ * chip reset */
+ public final long totalHotspot2ScanTimeMs;
+ /** The total time CCA is on busy status on the current frequency in ms counted from the last
+ * radio chip reset */
+ public final long totalCcaBusyFreqTimeMs;
+ /** The total radio on time of the current frequency from the last radio chip reset */
+ public final long totalRadioOnFreqTimeMs;
+ /** The total number of beacons received from the last radio chip reset */
+ public final long totalBeaconRx;
+
+ /** Constructor function {@hide} */
+ public WifiUsabilityStatsEntry(long timeStampMs, int rssi,
+ int linkSpeedMbps, long totalTxSuccess, long totalTxRetries,
+ long totalTxBad, long totalRxSuccess, long totalRadioOnTimeMs,
+ long totalRadioTxTimeMs, long totalRadioRxTimeMs, long totalScanTimeMs,
+ long totalNanScanTimeMs, long totalBackgroundScanTimeMs, long totalRoamScanTimeMs,
+ long totalPnoScanTimeMs, long totalHotspot2ScanTimeMs, long totalCcaBusyFreqTimeMs,
+ long totalRadioOnFreqTimeMs, long totalBeaconRx) {
+ this.timeStampMs = timeStampMs;
+ this.rssi = rssi;
+ this.linkSpeedMbps = linkSpeedMbps;
+ this.totalTxSuccess = totalTxSuccess;
+ this.totalTxRetries = totalTxRetries;
+ this.totalTxBad = totalTxBad;
+ this.totalRxSuccess = totalRxSuccess;
+ this.totalRadioOnTimeMs = totalRadioOnTimeMs;
+ this.totalRadioTxTimeMs = totalRadioTxTimeMs;
+ this.totalRadioRxTimeMs = totalRadioRxTimeMs;
+ this.totalScanTimeMs = totalScanTimeMs;
+ this.totalNanScanTimeMs = totalNanScanTimeMs;
+ this.totalBackgroundScanTimeMs = totalBackgroundScanTimeMs;
+ this.totalRoamScanTimeMs = totalRoamScanTimeMs;
+ this.totalPnoScanTimeMs = totalPnoScanTimeMs;
+ this.totalHotspot2ScanTimeMs = totalHotspot2ScanTimeMs;
+ this.totalCcaBusyFreqTimeMs = totalCcaBusyFreqTimeMs;
+ this.totalRadioOnFreqTimeMs = totalRadioOnFreqTimeMs;
+ this.totalBeaconRx = totalBeaconRx;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timeStampMs);
+ dest.writeInt(rssi);
+ dest.writeInt(linkSpeedMbps);
+ dest.writeLong(totalTxSuccess);
+ dest.writeLong(totalTxRetries);
+ dest.writeLong(totalTxBad);
+ dest.writeLong(totalRxSuccess);
+ dest.writeLong(totalRadioOnTimeMs);
+ dest.writeLong(totalRadioTxTimeMs);
+ dest.writeLong(totalRadioRxTimeMs);
+ dest.writeLong(totalScanTimeMs);
+ dest.writeLong(totalNanScanTimeMs);
+ dest.writeLong(totalBackgroundScanTimeMs);
+ dest.writeLong(totalRoamScanTimeMs);
+ dest.writeLong(totalPnoScanTimeMs);
+ dest.writeLong(totalHotspot2ScanTimeMs);
+ dest.writeLong(totalCcaBusyFreqTimeMs);
+ dest.writeLong(totalRadioOnFreqTimeMs);
+ dest.writeLong(totalBeaconRx);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<WifiUsabilityStatsEntry> CREATOR =
+ new Creator<WifiUsabilityStatsEntry>() {
+ public WifiUsabilityStatsEntry createFromParcel(Parcel in) {
+ return new WifiUsabilityStatsEntry(
+ in.readLong(), in.readInt(),
+ in.readInt(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong(), in.readLong(),
+ in.readLong(), in.readLong()
+ );
+ }
+
+ public WifiUsabilityStatsEntry[] newArray(int size) {
+ return new WifiUsabilityStatsEntry[size];
+ }
+ };
+}
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index b3ac9f1..226c7c48 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -26,6 +26,7 @@
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
import android.net.wifi.IWifiManager;
+import android.net.wifi.IWifiUsabilityStatsListener;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
@@ -464,4 +465,15 @@
public void stopDppSession() throws RemoteException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void addWifiUsabilityStatsListener(
+ IBinder binder, IWifiUsabilityStatsListener listener, int listenerIdentifier) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeWifiUsabilityStatsListener(int listenerIdentifier) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4fbef5a..5c2f626 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -61,6 +61,7 @@
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
import android.net.wifi.WifiManager.SoftApCallback;
import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.net.wifi.WifiManager.WifiUsabilityStatsListener;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -80,6 +81,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Unit tests for {@link android.net.wifi.WifiManager}.
@@ -103,7 +105,9 @@
@Mock SoftApCallback mSoftApCallback;
@Mock TrafficStateCallback mTrafficStateCallback;
@Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback;
+ @Mock WifiUsabilityStatsListener mWifiUsabilityStatsListener;
+ private Executor mExecutor;
private Handler mHandler;
private TestLooper mLooper;
private WifiManager mWifiManager;
@@ -1342,4 +1346,40 @@
assertArrayEquals(TEST_MAC_ADDRESSES, mWifiManager.getFactoryMacAddresses());
verify(mWifiService).getFactoryMacAddresses();
}
+
+ /**
+ * Verify the call to addWifiUsabilityStatsListener goes to WifiServiceImpl.
+ */
+ @Test
+ public void addWifiUsabilityStatsListeneroesToWifiServiceImpl() throws Exception {
+ mExecutor = new SynchronousExecutor();
+ mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+ verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+ any(IWifiUsabilityStatsListener.Stub.class), anyInt());
+ }
+
+ /**
+ * Verify the call to removeWifiUsabilityStatsListener goes to WifiServiceImpl.
+ */
+ @Test
+ public void removeWifiUsabilityListenerGoesToWifiServiceImpl() throws Exception {
+ ArgumentCaptor<Integer> listenerIdentifier = ArgumentCaptor.forClass(Integer.class);
+ mExecutor = new SynchronousExecutor();
+ mWifiManager.addWifiUsabilityStatsListener(mExecutor, mWifiUsabilityStatsListener);
+ verify(mWifiService).addWifiUsabilityStatsListener(any(IBinder.class),
+ any(IWifiUsabilityStatsListener.Stub.class), listenerIdentifier.capture());
+
+ mWifiManager.removeWifiUsabilityStatsListener(mWifiUsabilityStatsListener);
+ verify(mWifiService).removeWifiUsabilityStatsListener(
+ eq((int) listenerIdentifier.getValue()));
+ }
+
+ /**
+ * Defined for testing purpose.
+ */
+ class SynchronousExecutor implements Executor {
+ public void execute(Runnable r) {
+ r.run();
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
new file mode 100644
index 0000000..a947b55
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiUsabilityStatsEntryTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.validateMockitoUsage;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiUsabilityStatsEntry}.
+ */
+@SmallTest
+public class WifiUsabilityStatsEntryTest {
+
+ /**
+ * Setup before tests.
+ */
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Clean up after tests.
+ */
+ @After
+ public void cleanup() {
+ validateMockitoUsage();
+ }
+
+ /**
+ * Verify parcel read/write for Wifi usability stats result.
+ */
+ @Test
+ public void verifyStatsResultWriteAndThenRead() throws Exception {
+ WifiUsabilityStatsEntry writeResult = createResult();
+ WifiUsabilityStatsEntry readResult = parcelWriteRead(writeResult);
+ assertWifiUsabilityStatsEntryEquals(writeResult, readResult);
+ }
+
+ /**
+ * Write the provided {@link WifiUsabilityStatsEntry} to a parcel and deserialize it.
+ */
+ private static WifiUsabilityStatsEntry parcelWriteRead(
+ WifiUsabilityStatsEntry writeResult) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeResult.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return WifiUsabilityStatsEntry.CREATOR.createFromParcel(parcel);
+ }
+
+ private static WifiUsabilityStatsEntry createResult() {
+ return new WifiUsabilityStatsEntry(
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
+ );
+ }
+
+ private static void assertWifiUsabilityStatsEntryEquals(
+ WifiUsabilityStatsEntry expected,
+ WifiUsabilityStatsEntry actual) {
+ assertEquals(expected.timeStampMs, actual.timeStampMs);
+ assertEquals(expected.rssi, actual.rssi);
+ assertEquals(expected.linkSpeedMbps, actual.linkSpeedMbps);
+ assertEquals(expected.totalTxSuccess, actual.totalTxSuccess);
+ assertEquals(expected.totalTxRetries, actual.totalTxRetries);
+ assertEquals(expected.totalTxBad, actual.totalTxBad);
+ assertEquals(expected.totalRxSuccess, actual.totalRxSuccess);
+ assertEquals(expected.totalRadioOnTimeMs, actual.totalRadioOnTimeMs);
+ assertEquals(expected.totalRadioTxTimeMs, actual.totalRadioTxTimeMs);
+ assertEquals(expected.totalRadioRxTimeMs, actual.totalRadioRxTimeMs);
+ assertEquals(expected.totalScanTimeMs, actual.totalScanTimeMs);
+ assertEquals(expected.totalNanScanTimeMs, actual.totalNanScanTimeMs);
+ assertEquals(expected.totalBackgroundScanTimeMs, actual.totalBackgroundScanTimeMs);
+ assertEquals(expected.totalRoamScanTimeMs, actual.totalRoamScanTimeMs);
+ assertEquals(expected.totalPnoScanTimeMs, actual.totalPnoScanTimeMs);
+ assertEquals(expected.totalHotspot2ScanTimeMs, actual.totalHotspot2ScanTimeMs);
+ assertEquals(expected.totalCcaBusyFreqTimeMs, actual.totalCcaBusyFreqTimeMs);
+ assertEquals(expected.totalRadioOnFreqTimeMs, actual.totalRadioOnFreqTimeMs);
+ assertEquals(expected.totalBeaconRx, actual.totalBeaconRx);
+ }
+}